feat(triage-bot): let every flow capture debug-journal findings - #4949
Conversation
The bot has never been able to maintain the journal it reads before every investigation. Sessions tried repeatedly and reported the edit denied; the deeper reason is that sync_repo() runs `git reset --hard origin/main` and `git clean -fd` before every flow, so an edit inside the clone is destroyed before anything can pick it up. No skill asked for a journal entry either, so what upkeep happened was self-motivated and rare. Capture: a queue directory outside the clone, writable by all five flows via a single Edit grant in the shared base allowlist, and JOURNAL_CAPTURE_PROMPT appended to every flow's system prompt. The prompt is the only lever that reaches /code-review, whose SKILL.md this repo does not own. It asks for what was verified and how, and explicitly for silence when a run learned nothing that generalises. Flush: once a day, if anything is queued, /journal-update folds the queue in and opens a PR. It re-checks each candidate against current main before accepting it, and re-checks the existing journal against what has merged since - a file that only ever grows becomes wrong, and a wrong entry is worse than a missing one because every later run trusts it. Two entries were found stale this week; one asserted a credential leak that had been fixed, which would have had a triage run tell a reporter to rotate keys that never leaked. That flow is the only one that can both edit a file and push, so it has the narrowest edit scope of any: the journal and the cspell dictionary, named by exact path. It cannot write the queue it reads, cannot push to any branch outside bot/debug-journal-*, and cannot merge its own PR - the human merge is the review gate. Force-push denials match the PR flow's. Consumed candidates are moved to processed/ rather than deleted, so no rm grant is needed anywhere, a re-run cannot double-count, and a dropped finding stays auditable. Two pre-existing tests asserted the appended system prompt was exactly GH_API_ENDPOINT_FIRST_PROMPT; it is now that plus the capture text, so they assert containment instead. The property they protect - that the #4758 gh-api steer still reaches review and cleanup - is unchanged and now also covered directly. 36 new tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The new queue archiving logic can silently overwrite already-archived findings with the same filename, which undermines the stated auditability of processed/dropped entries.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR extends the triage-bot daemon to (1) append a system prompt that asks each flow to capture verified “debug journal” findings into a queue directory outside the repo clone, and (2) run a once-per-day /journal-update flow that folds queued entries into the debug journal via a PR with narrowly scoped edit/push permissions.
Changes:
- Add a shared
journal-queue/(outside the clone) and appendJOURNAL_CAPTURE_PROMPTto all flows so findings survivesync_repo()resets. - Add a daily gated
flush_journal()path that runs/journal-updateunder a dedicated allow/deny set and archives consumed queue entries. - Add the new
/journal-updateskill plus expanded unit tests for queue capture/gating/permissions; update the debug journal instructions and cspell dictionary.
File summaries
| File | Description |
|---|---|
| tools/triage_daemon.py | Adds queue + capture prompt, journal flush flow, and a dedicated permission set for /journal-update. |
| tools/test_triage_daemon.py | Adds unit coverage for queue behavior, prompt propagation to all flows, daily flush gating, and permission carve-outs. |
| .claude/skills/journal-update/SKILL.md | Introduces the /journal-update skill contract (verify candidates, re-check journal, run pre-commit, open PR). |
| .claude/skills/issue-triage/references/debug-journal.md | Updates contributor guidance to write findings to the queue under the triage bot. |
| .cspell/custom-dictionary-workspace.txt | Adds “oneline” to satisfy cspell in new docs/instructions. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| for entry in entries: | ||
| entry.replace(archive_dir / entry.name) |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Why
The triage bot reads
debug-journal.mdbefore every investigation, but has never been able to maintain it. Sessions tried repeatedly across the last fortnight (#3078, #4804, #4840, #4845, #4931) and each reported the edit denied — but permissions were only half the story.sync_repo()runsgit reset --hard origin/mainandgit clean -fdbefore every flow, so an edit inside the clone is destroyed before anything could pick it up. And no skill ever asked for a journal entry, so what upkeep happened was self-motivated and rare.Capture
A queue directory outside the clone, writable by all five flows through one
Editgrant in the shared base allowlist, plusJOURNAL_CAPTURE_PROMPTappended to every flow's system prompt. An appended system prompt is the only lever that reaches/code-review, whoseSKILL.mdthis repo doesn't own — same reasoning as the existingGH_API_ENDPOINT_FIRST_PROMPT.The prompt asks for what was verified and how, and explicitly asks for silence when a run learned nothing that generalises — an empty queue is the expected outcome for most runs.
Flush
Once a day, if anything is queued,
/journal-updatefolds the queue in and opens a PR. It does two things:mainbefore accepting it — fold in, rewrite, or drop with a reason.The second half is the point. A file that only ever grows becomes wrong, and a wrong entry is worse than a missing one because every later run trusts it. Two entries were found stale this week; one asserted a credential leak that #4910 had already fixed, which would have had a triage run tell a reporter to rotate keys that never leaked.
Blast radius
The flush is the only flow that can both edit a file and push, so it's deliberately the narrowest:
--add-dir).bot/debug-journal-*, so it cannot push tomaineven though that's where the file lives.Consumed candidates move to
processed/rather than being deleted: normgrant needed anywhere, a re-run can't double-count, and a dropped finding stays auditable.Tests
36 new, 190 total,
tools/test_triage_daemon.py(pre-commit gated). Cover the queue living outside the clone, capture reaching all five flows, the once-a-day gate, the permission set's carve-outs, and archive-on-success / keep-on-failure.Two pre-existing tests asserted the appended prompt was exactly
GH_API_ENDPOINT_FIRST_PROMPT; it's now that plus the capture text, so they assert containment. The property they protect — the #4758 gh-api steer still reaching review and cleanup — is unchanged and now covered directly too.Worth a look before merging
Edit(<clone>/**)has been granted since 2026-08-24 yet those sessions still reported Edit denied, which points at**not crossing the.claudepath segment. This PR sidesteps it (the queue is elsewhere, and the journal rule is an exact path rather than a glob), but I have not proved that hypothesis — the first real flush run will confirm whether the exact-pathEditrule fires. If it doesn't, that's the thing to look at.🤖 Generated with Claude Code