feat(standards): give shellcheck a canonical config like the other linters - #172
Merged
twistedmelonman merged 1 commit intoSep 18, 2026
Merged
Conversation
…nters shellcheck was the only linter in run-standards.sh with no canonical config. yamllint and markdownlint each resolve one from standards/; shellcheck ran bare and took whatever the machine offered. Because shellcheck searches ANCESTOR directories, a local run under a checkout whose parent carries a ~/.shellcheckrc silently picked up settings CI never saw -- 68 findings locally against CI's 24 on the same tree. That is claude-config#534. Resolution mirrors yamllint's at :147-150: a repo-root .shellcheckrc wins, else standards/shellcheckrc. Passing --rcfile explicitly is what stops the ancestor search, so this both centralizes the policy and closes the leak. Content is external-sources + source-path=SCRIPTDIR + disable=SC2310. The first two RESOLVE files sourced through a variable path rather than silencing SC1091, so the sourced code is analyzed instead of skipped. SC2310 is informational and fires throughout ordinary guard-clause style. `enable=all` is deliberately excluded. Measured across every clone with shell files on 2026-09-18: this config takes 21 of 24 repos to zero findings, while adding enable=all takes four otherwise-clean repos to 124+ combined (dotfiles 63, vpn-lan-bridge 12, claude-wrapper 10, scripts 3, LaunchAgents 1). A base that forces per-repo exceptions is not a base. enable=all stays in ~/.shellcheckrc as the stricter interactive config: local advisory, CI enforcing. Verified by running run-standards.sh itself against seven repos, not by reading the diff: github-workflows, dotfiles, claude-wrapper, vpn-lan-bridge, dev-env and claude-config all pass; kebab-tax fails with exactly the 12 findings the measurement predicted, all in one test file (10x SC2329, the standard false positive for a harness dispatching indirectly, plus 2x SC2155). That repo is addressed separately -- a file-level disable with a comment, not a repo-level config. The canonical file carries a known-bad gate in its own verification: a fixture with an unquoted expansion must still be rejected under it, so a config that silenced everything could not pass as clean. Advances twistedmelonman/claude-config#534. Claude-Session: https://claude.ai/code/session_01QkitU5UQAawPwZLEuXRajG
|
No blocking issues found. The VERDICT: PASS |
twistedmelonman
deleted the
claude/feat-canonical-shellcheckrc-7ba465dd
branch
September 18, 2026 18:18
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.
shellcheck was the only linter in
run-standards.shwith no canonical config. Every other linter resolves repo-local-else-canonical; shellcheck alone fell through to whatever.shellcheckrcthe filesystem search happened to find — including~/.shellcheckrc, outside the repo entirely. That is the structural cause of twistedmelonman/claude-config#534, and the reason a local run and CI could disagree with nothing in the repo to explain it.standards/shellcheckrcis that missing canonical file, and the shellcheck block now resolves it either/or, mirroring the yamllint block.The base, and why it is this base
Three effective settings:
external-sources=true,source-path=SCRIPTDIR,disable=SC2310.Measured across the 24 fleet repos carrying shell files: 21 are at zero findings under this base.
enable=allis deliberately excluded. Measured, it takes four otherwise-clean repos to 124+ combined findings (dotfiles 63, vpn-lan-bridge 12, claude-wrapper 10, scripts 3, LaunchAgents 1) and kebab-tax from 12 to 38. A base that forces per-repo exceptions is not a base. It stays in~/.shellcheckrcas the stricter interactive config, which is the right place for it.This matters more than it looks, because shellcheck rc files do not merge: a repo-root
.shellcheckrchalts the ancestor search outright, and--rcfilereplaces rather than layers. Any per-repo exception must restate the whole base and then silently drifts from it. Exceptions have to stay rare for the base to mean anything.Not in this PR
Steps 3-4 of #534 — deleting the three per-repo
.shellcheckrcfiles and annotating kebab-tax's one genuinely-excepted test file — are decided but deliberately left out. Deleting a repo's rc file exposes it to~/.shellcheckrcunless that repo's local hook passes the canonical path explicitly, so those deletions need their own PR and their own sequencing against this one landing.Advances twistedmelonman/claude-config#534.
https://claude.ai/code/session_011awg91UvzUos9YoXHJ2e8B