Skip to content

fix(lint): lint-skill.sh recognizes read as a variable binding, not just VAR= - #2490

Merged
carlos-alm merged 5 commits into
mainfrom
fix/issue-2344-lint-skill-count-fp
Aug 14, 2026
Merged

fix(lint): lint-skill.sh recognizes read as a variable binding, not just VAR=#2490
carlos-alm merged 5 commits into
mainfrom
fix/issue-2344-lint-skill-count-fp

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • lint-skill.sh's Check 1 (cross-fence variable usage) only recognized VAR=value as a variable assignment. read/read -r VAR1 VAR2 ... binds variables just as validly, but with no = after the name.
  • This caused a false positive on fixer/SKILL.md itself: its I4 integrity check re-derives a per-line $COUNT (expected occurrence count) via while IFS=$'\t' read -r F COUNT LINE; do ..., which collides in name only with the unrelated batch-size $COUNT set up in Phase 0. Since each ```bash fence is a separate bash invocation, there's no real collision at runtime — but the linter didn't recognize the later block's own fresh read-bound $COUNT, so it flagged the block's own local variable as if it were a stale cross-fence reference.
  • Fix: the assignment-collection pass now also recognizes read/read -r targets as variable bindings (same VAR_BLOCK/REASSIGNED bookkeeping used for VAR=), scoped to the same block via a register_var helper shared by both detection paths.

Verified this doesn't mask genuine cross-fence bugs: a var assigned in one block and referenced in a later block without any read/= rebinding in that later block is still correctly flagged. Ran the patched linter against every SKILL.md in the repo and diffed error/warning counts against the pre-fix version — only fixer/SKILL.md changed (2 errors → 0), every other skill's output is byte-identical.

Test plan

  • New regression test tests/unit/lint-skill-read-binding-2344.test.ts: does-not-flag (read-rebinding case), still-flags (genuine leak), still-flags (leak past an unrelated read in the same block), and a direct assertion that fixer/SKILL.md itself produces no $COUNT error
  • Full test suite: 327 files / 5245 tests passed
  • npm run lint clean
  • diff-impact --staged — no function-level impact (shell script + new test file)
  • Manually diffed lint-skill.sh output across all .claude/skills/*/SKILL.md before/after — no regressions

Closes #2344

… just VAR=

Check 1 (cross-fence variable usage) only tracked VAR=value assignments,
so a var re-bound via `read`/`read -r VAR1 VAR2` in a later block looked
identical to a stale reference leaking from an earlier block. This false-
flagged fixer/SKILL.md's own I4 integrity check, where a loop-local $COUNT
(bound via `read`) collides in name only with the unrelated batch-size
$COUNT set in Phase 0.

Closes #2344
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR updates the skill linter to recognize variables bound by Bash read commands while preserving detection of genuine cross-fence references.

  • Centralizes assignment bookkeeping through register_var.
  • Adds sanitization for prompt, redirection, and value-taking option operands before collecting read destinations.
  • Adds regression coverage for ordinary, array, readline, and combined-option forms.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
.claude/skills/create-skill/scripts/lint-skill.sh Adds block-local read binding recognition and incorporates the prior review fixes for non-destination operands and combined options.
tests/unit/lint-skill-read-binding-2344.test.ts Covers the intended rebinding behavior, genuine leaks, prompt and option operands, array destinations, and combined short options.

Reviews (5): Last reviewed commit: "fix(lint): replace per-line subprocess p..." | Re-trigger Greptile

Comment thread .claude/skills/create-skill/scripts/lint-skill.sh
Greptile review on PR #2490: the read-binding scan treated every
uppercase token after `read` as a bound destination, so a quoted
prompt string (-p "..."), a here-string/redirection operand, or a
value-taking flag's own argument (-t/-u/-d/-n/-N) could contain an
unrelated uppercase word and get misread as a binding — silently
suppressing a genuine cross-fence reference to that word elsewhere
in the block.

Now strips redirections, quoted arguments, and value-taking flags'
own arguments before extracting destination names, and excludes any
`$`-prefixed token (a reference, not a binding).

Filed #2491 for a separate, pre-existing false-negative in the
reference-checking side (a blanket "read " substring exemption) that
predates this PR and is out of scope here.
Comment thread .claude/skills/create-skill/scripts/lint-skill.sh Outdated
Greptile round 2 on PR #2490: -i takes a value argument just like
-p/-t/-u/-d/-n/-N, but was missing from the value-taking-flag strip
list, so an uppercase -i argument (readline initial text) could still
be misread as a bound destination. -a is deliberately left out of the
strip list — its argument is the array actually being read into, a
genuine destination, not a value.
Comment thread .claude/skills/create-skill/scripts/lint-skill.sh
…ng flag

Greptile round 3 on PR #2490: the strip regex only matched a
standalone single-letter flag (-i FOO), not a combined cluster like
-ei FOO where -e takes no argument but the trailing -i does. Matches
any -[letters] cluster whose LAST letter is a value-taking flag,
so its following token is stripped as that flag's value rather than
extracted as a destination. -a stays excluded even combined (-ra ARR
still keeps ARR as the genuine array destination).
Windows CI failure on PR #2490: a new test now runs lint-skill.sh
directly against the real fixer/SKILL.md (by far the largest SKILL.md
in the repo, ~850 non-comment bash-block lines) and timed out at the
default 30s testTimeout. Check 1's assignment-collection loop forked
an echo|grep|sed pipeline unconditionally for every such line — a few
seconds on macOS/Linux, but Windows process creation is ~100x slower
per this file's own header comment, so a file this size was already
at a latent performance cliff pre-existing this PR; a direct per-file
test is what first tripped it.

Replaces the pipeline with a pure-bash extraction loop ([[ =~ ]] +
parameter expansion), matching the file's stated design (every OTHER
check already avoids subprocess forking for this reason). Cuts wall
time against fixer/SKILL.md from ~4.0s to ~0.95s on macOS. Verified
byte-identical error/warning counts across every SKILL.md in the repo
before and after.

Also bumps the new real-file test's own timeout to 120s as a safety
margin, since Windows fork overhead is inherently harder to predict
precisely than to bound generously.
@carlos-alm
carlos-alm merged commit c4b5632 into main Aug 14, 2026
23 checks passed
@carlos-alm
carlos-alm deleted the fix/issue-2344-lint-skill-count-fp branch August 14, 2026 03:27
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

follow-up: lint-skill.sh flags a cross-fence $COUNT violation in fixer/SKILL.md (possible false positive)

1 participant