Consistency audit phase 2: retire the comment addresser¶
Master plan: PLAN-consistency-audit.md
Planning effort: medium. The deletion itself is trivial. The work is in the residue: four files come out, but five more mention them, and one of those mentions is the stated reason for a guard condition that must survive the deletion it justifies.
Scope¶
In scope:
- Issue #360 -- delete
.github/workflows/pr-address-comments.yml,tools/address-comments-with-claude.sh,tools/render-review.pyandtools/review-schema.json, in a single commit, as the audit spec requires. kerbside/tests/unit/test_render_review.py, which cannot outliverender-review.py.- The five surviving references to the deleted files:
one row in
.claude/CLAUDE.md, three comments in.github/workflows/pr-re-review.yml, and one intools/shellcheck-wrap.sh. - The four review marks and their weAudit entries.
Out of scope:
- The rest of the
ci-review-automationaudit. Every other measured criterion already passes; see the survey below. - The reviewer-confirmed half of that audit spec (prompt
wording,
pr-fix-tests.yml). Not measured, not failing, and not what #360 is about. - Moving the CI workflow inventory out of
.claude/CLAUDE.md. That list arguably belongs indocs/testing.mdunder the documentation discipline, but relocating it is a separate argument and would bury this phase's security change in a documentation reshuffle. Recorded as Future work.
What the survey found¶
The master plan's phase 2 sketch was written on 2026-08-29 and is accurate about the shape of the work. Two of its specifics were wrong, and both are corrected at source in the master plan as part of the planning commit, so this is the only place they need reading twice.
The four files and the test all exist, at
.github/workflows/pr-address-comments.yml (237 lines),
tools/address-comments-with-claude.sh (592),
tools/render-review.py (356), tools/review-schema.json (96)
and kerbside/tests/unit/test_render_review.py (105). The test
resolves parents[3] / 'tools' / 'render-review.py' and calls
_spec.loader.exec_module at module scope, so it fails at
collection rather than as a test failure -- tox -e py3 stops
producing results at all.
pr-re-review.yml carries three references, not one. The
sketch names line 10 only. The other two matter more than it
does:
- Line 10 -- "pr-address-comments.yml splits for the same reason", justifying why trigger and review are separate jobs. Becomes false; the reasoning stands on its own without it.
- Lines 34-37 -- the stated reason for the live
github.event.comment.user.type != 'Bot'guard is that "pr-address-comments.yml quotes model-generated summaries back into the pull request, and a summary that mentions a trigger phrase would re-fire the lane." The guard must stay; only its justification is being deleted.pr-auto-review.ymlstill posts bot comments carrying model-generated prose, so the hazard survives its stated cause. - Line 106 -- "pr-address-comments.yml has always done this;
this file did not", a historical contrast explaining
persist-credentials: false. The rationale above it is self-contained; the contrast dangles.
tools/shellcheck-wrap.sh line 7 also refers to it, and the
sketch does not mention this at all. Its header comment claims
"the only mentions in .github/workflows are comments in
pr-address-comments.yml explaining why it is deliberately
skipped there". After this phase there are no such mentions, so
the sentence describes a file that does not exist.
Nothing in docs/ refers to the addresser. docs/testing.md
does not list any of the three bot-triggered workflows. The only
inventory is the .claude/CLAUDE.md row at line 139. Two
historical plan files mention it
(PLAN-demo-install-phase-04-ci-lane.md line 157,
PLAN-two-tier-ci-phase-03-merge-queue.md lines 271 and 488);
those are records of past work and must not be edited.
The workflow is live, not dormant. It triggers on every
issue_comment created, and job-level if: conditions skip it.
gh run list shows five runs in the six days to 2026-08-29, all
skipped. So "unused" means never authorised past the trigger
gate, not never invoked -- the contents: write at line 33-36
is granted on each of those runs.
Every other measured criterion of the audit already passes,
verified individually against the spec at
development/docs/audits/ci-review-automation.md:
pr-re-review.yml and pr-retest.yml both exist;
pr-re-review.yml reaches shakenfist/actions/pr-bot-trigger@main
(line 55) and shakenfist/actions/review-pr-with-claude@main
(line 115); the only secrets: inherit in the tree is on
export-repo-config.yml (line 15), which the spec names
explicitly as a correct use. The automated_reviewer job at
functional-tests.yml line 1076 is not the hand-written
kind the spec supersedes -- it is a call to
shakenfist/actions/.github/workflows/pr-auto-review.yml@main
with the right permissions block. Leave it alone.
Decisions¶
1. The deletion is one commit, and it carries the security
argument. The audit spec says "Remove all four in one commit",
so the four files plus the orphaned test land together. The
commit message is where the reason for the phase lives: a
workflow holding contents: write while checking out pull
request code, for automation superseded by
shakenfist/actions/review-pr-with-claude@main. A reader six
months from now finds that in git log, not in a plan file.
2. Reference repair is a separate commit from the deletion. Keeping them apart means the deletion commit is exactly the four files and the test, and reviewable as such against the spec. The alternative -- one commit for everything -- would satisfy the audit equally, since the spec constrains the four files rather than forbidding company. Separation is for the human reviewer, not the checker.
3. The bot-comment guard survives; only its justification
changes. This is the decision most likely to be argued with,
because the tempting reading is that deleting the addresser
deletes the reason for the guard. It does not. The guard exists
because bot-posted comments can quote model prose containing a
trigger phrase, and pr-auto-review.yml posts exactly such
comments today. Removing the guard along with its stale comment
would introduce a self-retriggering lane, which is a worse
outcome than the audit failure this phase is fixing.
4. Review marks are pruned in this branch, and the prune
commit is not signed. Two things make this non-obvious.
First, review-tracking.py's cmd_prune reads
blob_sha('HEAD:%s' % path), so it only sees a deletion once
that deletion is committed -- the prune must run after step 2a,
never before. Second, the project convention that review-mark
commits are signed applies to marks being added:
prune-reviews.yml's own header states that "prune only ever
removes marks, so the commit does not need to be signed -- the
attestations live in the signed commits that introduced the
stamps." Pruning by hand here rather than leaving it to the
post-merge workflow keeps the branch internally consistent, so
REVIEWS.md never names a file the same branch deleted.
Step plan¶
| Step | Effort | Model | Isolation | Brief for sub-agent |
|---|---|---|---|---|
| 2a | low | haiku | none | In the kerbside worktree at /srv/kasm_profiles/mikal/vscode/src/shakenfist/kerbside-wt-consistency-p2, git rm exactly five files in one commit: .github/workflows/pr-address-comments.yml, tools/address-comments-with-claude.sh, tools/render-review.py, tools/review-schema.json and kerbside/tests/unit/test_render_review.py. Change nothing else -- the dangling references in other files are step 2b's job, and touching them here would break the audit's "all four in one commit" requirement into something harder to review. The commit message body must state the security reason: the workflow granted contents: write at workflow scope while checking out pull request code, for automation superseded by shakenfist/actions/review-pr-with-claude@main and never authorised past its trigger gate in practice. Then run tox -e py3 and confirm it still collects and passes -- before this commit it would have failed at collection had render-review.py gone alone. |
| 2b | medium | sonnet | none | Repair the five references to files step 2a deleted. (1) .claude/CLAUDE.md line 139: delete the whole list row - `pr-address-comments.yml` - Bot-triggered comment addressing, leaving the pr-re-review.yml and pr-retest.yml rows. (2) .github/workflows/pr-re-review.yml line 10: delete the sentence "pr-address-comments.yml splits for the same reason." -- the paragraph reads correctly without it. (3) Same file, lines 34-37: the if: guard github.event.comment.user.type != 'Bot' must stay exactly as it is; rewrite only the comment above it so the hazard is attributed to what still causes it -- pr-auto-review.yml posts bot comments containing model-generated review prose, and a phrase inside one would re-fire the lane, since contains() does not care that the phrase is inside a quote. Do not remove the guard. (4) Same file, line 106: delete the trailing sentence "pr-address-comments.yml has always done this; this file did not." -- the four lines of rationale above it stand alone. (5) tools/shellcheck-wrap.sh line 7: the header claims "the only mentions in .github/workflows are comments in pr-address-comments.yml explaining why it is deliberately skipped there"; rewrite that clause so it says pre-commit is not invoked by any workflow or tox environment, without citing a file that no longer exists. Do not edit docs/plans/PLAN-demo-install-phase-04-ci-lane.md or docs/plans/PLAN-two-tier-ci-phase-03-merge-queue.md: those are historical records of completed work. Finish with pre-commit run --all-files, which includes actionlint over the edited workflow. |
| 2c | low | haiku | none | Prune the review marks for the deleted files. This must run after step 2a is committed, because review-tracking.py compares against HEAD:<path> and cannot see an uncommitted deletion. From the worktree run tools/review-tracking.sh prune. Expect it to report four paths as gone: .github/workflows/pr-address-comments.yml, tools/address-comments-with-claude.sh, tools/render-review.py and tools/review-schema.json. It rewrites .vscode/mikal.weaudit, .vscode/mikal.weaudit-shas.json and regenerates REVIEWS.md. Never hand-edit any of those three. Commit the result on its own; this commit does not need to be signed, because prune only removes marks (see prune-reviews.yml's header comment for why). If prune reports any path beyond those four, stop and report -- that means develop moved under the branch and something unrelated went stale. |
| 2d | medium | sonnet | none | Verify the phase. From the worktree run, and report each result verbatim including failures: (1) grep -rn -E 'pr-address-comments\|address-comments-with-claude\|render-review\|review-schema' --exclude-dir=.git . and confirm the only remaining hits are in docs/plans/, which are historical records; (2) pre-commit run --all-files; (3) tox -e py3 and tox -e flake8; (4) python3 /srv/kasm_profiles/mikal/vscode/src/shakenfist/development/scripts/audit-check.py --repo-path <worktree> --repo-name kerbside, confirming the failure count has dropped from 3 to 2 and that ci-review-automation now passes; (5) grep -n "user.type != 'Bot'" .github/workflows/pr-re-review.yml returns a hit, proving step 2b did not remove the guard along with its comment. Fix nothing you find here without reporting it first. |
Each step is its own commit:
- 2a:
Retire the pull request comment addresser. - 2b:
Repair references to the retired addresser. - 2c:
Prune review marks for the retired addresser. - 2d: no commit (verification).
Risks and mitigations¶
| Risk | Mitigation |
|---|---|
Step 2b removes the user.type != 'Bot' guard along with the stale comment that explains it, creating a lane that re-fires on its own review comments. |
Decision 3 states the guard survives, 2b's brief says so twice in bold terms, and 2d greps for the guard as a done-criterion. This is the single most likely way to get this phase wrong. |
| The deletion commit includes the reference repairs, and a reviewer can no longer check it against the audit spec's "all four in one commit" wording. | Decision 2 splits them, and 2a's brief explicitly forbids touching anything else. |
Prune runs before the deletion is committed and reports nothing, so REVIEWS.md keeps naming four files that no longer exist. |
2c's brief states the ordering and the reason (HEAD:<path>), and names the four paths it should report as gone. If it reports zero, the ordering was wrong. |
tox -e py3 fails at collection because test_render_review.py was left behind while render-review.py went. |
The test is in 2a's deletion list, and 2a ends by running tox -e py3. The failure mode is loud rather than subtle -- no tests run at all. |
| A historical plan file gets "fixed" to remove a now-dangling reference, rewriting the record of completed work. | 2b names the two files and forbids editing them; 2d expects hits in docs/plans/ and treats them as correct. |
| The phase lands but #360 stays open because nothing re-runs the audit. | It closes on the next daily audit run. Do not close it by hand -- the master plan's success criteria require closure by a passing run, so a hand-closed issue hides whether the fix worked. |
Definition of done¶
- None of
pr-address-comments.yml,address-comments-with-claude.sh,render-review.pyorreview-schema.jsonexists anywhere in the tree. This command prints only paths underdocs/plans/:
grep -rln -E 'pr-address-comments|address-comments-with-claude|render-review|review-schema' \
--exclude-dir=.git .
- The four deletions plus
test_render_review.pyare a single commit, andgit show --staton it lists exactly five files. -
.github/workflows/pr-re-review.ymlstill containsgithub.event.comment.user.type != 'Bot', and the comment above it names a workflow that exists. -
REVIEWS.mdnames none of the four deleted files, and was regenerated bytools/review-tracking.sh prunerather than hand-edited. -
tox -e py3collects and passes;tox -e flake8passes;pre-commit run --all-filesis clean. -
audit-check.py --repo-path <worktree> --repo-name kerbsidereports 2 failures, not 3, andci-review-automationis not among them. - Issue #360 is closed by a passing consistency audit run, not by hand.
Back brief¶
The phase deletes automation rather than adding any, so the review question is not "does it work" but "what did it take with it". Two gates:
- Gate 1, before step 2b commits. The rewritten comment above
the
user.type != 'Bot'guard inpr-re-review.ymlis the one piece of new prose in this phase, and it is load-bearing: it is the only record of why that condition exists. Show the operator the before and after of that comment block, and the unchangedif:below it, before committing. - Gate 2, after step 2d. Report the
audit-check.pyfailure list. Ifci-review-automationstill fails after all four files are gone, the checker is matching something this survey did not find, and that is worth understanding before the phase is called done rather than after.
Phase 1's issues (#368, #370, #373) were still open when this plan was written, waiting on the daily audit run that had not yet fired since the merge. If they are still open when this phase lands, that is a signal about the audit, not about phase 1 -- check whether the run happened before assuming either phase failed.