Never write inside the analyzed sources: fix the random project-mode linter crashes - #8720
Merged
Conversation
Four linters generated a temporary ignore file at the root of the linted repository and deleted it after their run (secretlint, sqlfluff, cljstyle, coffeelint). A file appearing then disappearing there aborts the project-mode linters walking the tree at the same moment: trivy fails the whole run with "walk dir error: unknown error with .megalinter-secretlintignore: no such file or directory". Being a race, it turns random builds red. MegaLinter now writes only in REPORT_OUTPUT_FOLDER: - secretlint uses the generic ignore-file forwarding, whose generated file already lands in the report folder - sqlfluff receives excluded directories through the ignore_paths key of a generated configuration copied to the report folder, its user configuration being preserved - cljstyle uses its native repeatable --ignore argument, which adds patterns to the ignore set of the user .cljstyle instead of replacing it, so the custom CljstyleLinter class is not needed anymore - coffeelint is disabled: CoffeeScript tooling is discontinued and the tool has no exclusion option at all, reading .coffeelintignore from its working directory only The cli_lint_mode_project_exclude_workspace_file_name property and the write_workspace_generated_file helper are removed, including from the descriptor JSON schema so that a descriptor reintroducing a write in the sources fails build validation. REPORT_OUTPUT_FOLDER is also always excluded now, even when EXCLUDED_DIRECTORIES is overridden, and it is forwarded to project-mode linters even when it does not exist yet when their command line is built: reporters keep writing into it while linters run.
nvuillam
requested review from
Kurt-von-Laven,
bdovaz and
echoix
as code owners
August 11, 2026 21:59
Contributor
✅
|
…rding sqlfluff reads ignore_paths only from a config file discovered between the working directory and the analyzed path (core/linter/discovery.py _iter_config_files), so a generated configuration passed with --config is never consulted for path exclusions and the .wireit poison fixture was still linted. Like coffeelint, sqlfluff simply gets no forwarding: its poison fixture is removed and the limitation is documented in the descriptor.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Follow-up of #8718, where the CI turned red on an unrelated trivy crash.
The bug
Four linters generated a temporary ignore file at the root of the linted repository and deleted it after their run. Any project-mode linter walking the tree at that moment sees a file that vanishes under it:
Being a race between two linters running in parallel, it turns random builds red — and it can hit any repository, not just this one.
The invariant
MegaLinter never creates, modifies or deletes anything inside the analyzed sources; everything it generates goes to
REPORT_OUTPUT_FOLDER. (APPLY_FIXESrewriting the sources stays the one deliberate exception.)Each of the four linters was checked against its upstream capabilities rather than being special-cased:
REPOSITORY_SECRETLINT.megalinter-secretlintignoreat workspace root--secretlintignoreaccepts a workspace-relative pathSQL_SQLFLUFF.sqlfluffignoreat workspace rootprojectmode, documented in the descriptordiscovery.pyreadsignore_pathsonly from config files found by_iter_config_files(), i.e. between the working directory and the analyzed path: a config passed with--configis never consulted for path exclusionsCLOJURE_CLJSTYLE.cljstyleat workspace root, written by a custom class--ignoreargument — the custom class is deletedmain.clj:--ignore PATTERN,:assoc-fn conj;config/ignored? config (u/option :ignore) fileshows CLI patterns add to the user's.cljstyleignore set instead of replacing itCOFFEE_COFFEELINT.coffeelintignoreat workspace root--helphas no exclusion option at all, and.coffeelintignoreis read fromprocess.cwd()onlysqlfluff loses exclusion forwarding in
projectlint mode (its defaultlist_of_filesmode is unaffected, as MegaLinter filters the files itself there). It was already conditional before: MegaLinter only wrote the ignore file when the repository had none.cljstyle on the contrary gains behavior: exclusions used to be skipped entirely when the repository already had a
.cljstyle, they are now always applied and the user's own ignore patterns are kept.Preventing a comeback
cli_lint_mode_project_exclude_workspace_file_nameandwrite_workspace_generated_file()/cleanup_workspace_generated_files()are gone, including from the descriptor JSON schema, so a descriptor reintroducing a write in the sources now fails build validation.claude/rules/descriptors.mddocuments the invariant and lists three mechanisms instead of four (native flag, ignore file in the report folder, generated config)Also fixed
REPORT_OUTPUT_FOLDERis always excluded, even when the user overridesEXCLUDED_DIRECTORIES(it used to be only a default value, silently dropped by an override)Tests
linter_test.py: the report folder is forwarded even when absent, other directories only when they existutils_test.py: the report folder survives anEXCLUDED_DIRECTORIESoverride.wireitpoison fixtures ofclojure-styleandcredentialskeep guarding the two remaining forwardings:test_success_project_lint_modefails if either regresses. Thesqlone is removed, since sqlfluff no longer receives exclusionsRelation to #8633
#8633 fixes the same class of problem for secretlint and jscpd with a deeper rework of the secretlint ignore model. Both converge on "generated files live in the report folder"; they overlap on
repository.megalinter-descriptor.yml,Linter.pyandutils.py, so whichever merges second needs a rebase.