workflow-fix: lint leg resolving line-anchored gotchas.md L<n> citations (trim renumbering silently rots them)
Highlight text on any card (body, plan, or an event) to anchor a comment, or leave a whole-task comment here. Mention @claude to summon a reply.
No comments yet.
Goal
Add a workflow_lint.py leg that resolves LINE-ANCHORED gotchas.md L<n> citations in
the workflow surface against the line's expected content, so line-anchor drift is caught
mechanically instead of silently rotting.
Why
.claude/rules/gotchas.md is machine-appended (consolidate_lessons.py) and periodically
hand-trimmed to stay under the 200,000-byte GOTCHAS_SIZE_WARN_BYTES budget. Every
trim renumbers the file, so any citation elsewhere in the workflow surface that points at
gotchas.md L<n> drifts. The existing guard,
tests/test_workflow_lint_stale_gotchas_pointers.py, pins SUBSTRINGS only — it cannot see
a line-number anchor that now points at the wrong entry.
Surfaced by the round-3 code-reviewer on task #2280 (workflow-surface prose follow-up,
Rule 12: mechanizable + recurring). The reviewer reported four line-anchored citations at
gotchas.md L240 — .claude/agents/code-reviewer.md:606,
.claude/rules/code-reviewer-section-reference.md:1233 and :1245, and
.claude/agents/codex-code-reviewer.md:345 — which were already ~6 lines stale at
#2280's merge-base, i.e. pre-existing on trunk and NOT a #2280 regression. #2280's own
trim (201,850 -> 197,869 B) shifts them further.
Verification note for the implementer: the orchestrator's own attempt to independently
re-locate these anchors did not complete (a recursive regex grep over .claude timed out
on the shared VM under fleet load), so the four sites above are REVIEWER-REPORTED and
must be re-confirmed as step one — including the exact anchor spelling, since a plain
grep -rn "gotchas.md L[0-9]" over .claude/** returned no hits for that literal form.
Establish the real citation grammar in use before writing a matcher for it.
Scope
- Confirm the anchor sites and the actual textual form(s) line-anchored gotchas citations
take across
.claude/agents/,.claude/rules/,.claude/skills/, andCLAUDE.md. - Add a lint leg that, for each such citation, reads the cited line and checks it against an expected-content token carried in the citation (or flags the citation as unresolvable). Bundle it into the no-flags default run only if it is fast and deterministic; otherwise gate it behind its own flag and wire it where the other gotchas legs run.
- Repair the four (re-confirmed) stale anchors.
- Consider the stronger structural fix and record the decision either way: replace
line anchors with a stable per-entry token (a slug or the entry's
#Ncitation) so a trim cannot invalidate them at all. A matcher that only detects drift still needs a human to fix each drift; anchors that cannot drift end the class.
Acceptance criteria
- A deliberately drifted line anchor FAILs the new leg (fixture-backed, both directions: correct anchor passes, drifted anchor fails).
- The four re-confirmed live anchors resolve correctly after repair.
uv run python scripts/workflow_lint.py(no flags) rc=0.tests/test_workflow_lint_stale_gotchas_pointers.pystill green (this leg complements the substring pins; it does not replace them).
Provenance
Surfaced by the Claude code-reviewer during task #2280 round 3 (the gotchas archaeology
trim that unblocked #2280's Step 10d size gate). Filed rather than fixed in #2280 because
repairing four additional always-on spec files at merge time would have been an unreviewed
scope expansion on a task already gate-blocked — and because the anchors were stale on
trunk before #2280 existed.
workflow_fix_target: scripts/workflow_lint.py, .claude/agents/code-reviewer.md, .claude/rules/code-reviewer-section-reference.md, .claude/agents/codex-code-reviewer.md
Anchor sites + citation grammar — CONFIRMED (orchestrator, post-filing)
The re-confirmation this body asked for as step one has since COMPLETED (the earlier probe had timed out mid-turn, which is why the sites were originally filed as reviewer-reported). All four sites are real, and the grammar is now pinned:
.claude/rules/code-reviewer-section-reference.md:1233:gotchas.md` L240
.claude/rules/code-reviewer-section-reference.md:1245:gotchas.md` L240
.claude/agents/codex-code-reviewer.md:345:gotchas.md` L240
.claude/agents/code-reviewer.md:606:gotchas.md` L240
The citation form is `.claude/rules/gotchas.md` L240 — the closing BACKTICK falls
between the filename and the line token. That is why the obvious matcher
grep -rn "gotchas.md L[0-9]" returns ZERO hits and reads as "no line anchors exist": the
backtick breaks the adjacency the pattern assumes. Any matcher this task writes must
tolerate the backtick (and, defensively, other markdown punctuation) between the path and
the L<n> token — a naive adjacency regex will silently pass a file full of drifting
anchors, which is the failure mode that let these four rot unnoticed in the first place.
Secondary note for whoever writes the scanner: a broad character-class regex over
.claude/** tripped ugrep's complexity limit ("exceeds complexity limits") on this repo,
so prefer a narrow literal-anchored pattern or a small Python scan over an elaborate regex.
Cited content at the anchor: the SLURM_GPUS_ON_NODE / CUDA_VISIBLE_DEVICES entry — useful
as the expected-content token when checking whether L240 still resolves to the intended
entry.