EPS
← All tasks·#2471Proposed

workflow-fix: lint leg resolving line-anchored gotchas.md L<n> citations (trim renumbering silently rots them)

kind: infra#wf-fix
track:

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

  1. Confirm the anchor sites and the actual textual form(s) line-anchored gotchas citations take across .claude/agents/, .claude/rules/, .claude/skills/, and CLAUDE.md.
  2. 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.
  3. Repair the four (re-confirmed) stale anchors.
  4. 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 #N citation) 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.py still 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.

Activity