Skip to content

fix: triage prompt carries the questions, not the whole report - #164

Merged
STRML merged 7 commits into
mainfrom
fix/triage-prompt-questions-only
Oct 27, 2026
Merged

STRML merged 7 commits into
mainfrom
fix/triage-prompt-questions-only

Conversation

@STRML

@STRML STRML commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Why

The morning triage session was refused every time it launched. Session 9fdd76e2 (2026-10-27) and two earlier ones:

⏺ API Error: Opus 5.5's safeguards flagged this session
  Details: `[cyber]`   request req_011CgSq7odN7CDW2ZSYaFwfy

The prompt was the literal word go. The payload was this launcher:

bin/review.sh
  --append-system-prompt "<whole 25,802-byte report>"
  "go"

The 2026-10-26 report narrates a night of TV firmware work (sdb, root status, dlopen, W^X by mount). Reading that report is what gets refused, not the triage task:

request result
Read .../dreams/2026-10-26.md refused
Read .../dreams/2026-10-26.md (questions section alone) pass
Read .../dreams/2026-10-25.md (report with no such body) pass
go with the report in --append-system-prompt refused
go with nothing appended pass

What changed

  • --append-system-prompt carries the ## Open questions for the user section only (2.2KB on that report), capped at 12KB, plus the report path for a question that needs one number from it.
  • The launch prompt is Start the triage: restate the first open question and recommend one action. instead of go.
  • A report with no such section still launches, with an empty well-formed block, so an unusual report degrades to "nothing inlined" instead of "no session".
  • tests/review-prompt.sh pins all five properties (questions inlined, findings body and other sections absent, no Read the report instruction, real launch prompt, section-less fallback, cap). Registered in tests/run-all.sh and the CI matrix; CHANGELOG, CLAUDE.md and the codemap say why.

Tests

tests/review-prompt.sh   12 passed, 0 failed
tests/review-skip.sh     23 passed, 0 failed
tests/review-cmux.sh     45 passed, 0 failed

Verified end to end against the real report: the request captured from a stubbed launch (4,815-byte system prompt) now answers instead of refusing.

STRML added 7 commits October 27, 2026 09:53
The morning triage injected the entire report through
--append-system-prompt (~25KB on 2026-10-26) and launched with the literal
prompt 'go'. A report body narrating a night of firmware work (sdb, root
status, dlopen) made every launch read as an offensive-security engagement:
Opus 5.5 answered 'safeguards flagged this session ... Details: [cyber]'
before the first reply, and 'claude -p' with the same argv reproduced it.
The same report's '## Open questions for the user' section passes.

- inline only that section (12KB cap) and point at the report path
- replace 'go' with a real one-line instruction
- a section-less report still launches, with an empty block
- tests/review-prompt.sh pins all of it; wired into run-all and CI
- cap with awk on a whole-question boundary into a second mktemp file, so a
  cut never splits a question and never lands in a 0644 file created by a
  shell redirect
- mark the block truncated="1" and say the rest is in the report, instead
  of claiming the questions are quoted in full
- extract with fence tracking and an exact heading match: a fenced example
  heading no longer starts the section, a fenced heading inside a question
  no longer ends it, and a longer heading is a different section
- fail loudly when mktemp/awk/mv fail instead of launching with partial
  questions
- remove the questions temp file before exec; the trap names every temp file

tests/review-prompt.sh covers each one (24 assertions).
- fence detection covers ~~~ as well as ```, tracking which char opened the
  fence, so a tilde-fenced example heading does not start the section and a
  tilde-fenced heading inside a question does not end it
- the cap works in whole questions: a top-level list item plus every line
  under it is kept or dropped as a unit, so a question heading can no longer
  survive without the decision context beneath it
Round 2's patches left two markdown edge cases, both from the same
approximation: extraction and the cap each decided for themselves where a
question began. Now a single awk pass owns both.

- a fence opens on >=3 backticks or tildes and closes only on the same char
  at >= the opening length, so a 3-backtick line inside a 4-backtick block is
  content, not a close
- a question starts at a top-level list item only outside a fence, so a
  `- option` inside a fenced example cannot start or end one
- questions are kept or dropped whole, including their fenced blocks
- the truncated flag comes from the same pass that applied the cap

tests/review-prompt.sh: 37 assertions, including four-backtick nesting and a
fenced list item at the cap boundary.
A fence line may carry an info string (a language label after the fence run).
Only the leading run counts, and only a bare run closes a fence, so a labelled
block inside a question neither ends the section nor starts a question, and a
labelled example before the section is not the section.
Five review rounds on review.sh alone kept finding new markdown edge cases
(fenced example heading, tilde fences, fence length, fence info strings)
because four consumers each had their own idea of where the section ends.

Replacing that with the producer's own boundary:

- PROMPT.md: L2 wraps the section in AUTODREAM_QUESTIONS_BEGIN/END
- report-format.sh: report_questions + report_has_questions_block, the one
  reader; the old heading read is the legacy fallback inside it
- notify.sh, question-streaks.sh, review.sh: call the reader, stop parsing
- run.sh: a report from AUTODREAM_QUESTIONS_EPOCH on is truncated when its
  block is missing or unclosed, so a bad block is rebuilt, not guessed at;
  the empty-day stub emits the block
- review.sh: no markdown parsing at all, and the 12KB cap is all-or-nothing

tests: report-format.sh gains the block matrix; review-prompt.sh is rewritten
around the contract (23 assertions).
- the block reader takes the LAST end sentinel, so a question that quotes the
  sentinel in a code example cannot cut the block short
- the legacy fallback keeps fence tracking: a heading inside a code example is
  not the end of the section
- an oversized LEGACY report is sent to its heading section in the prompt,
  not to sentinels that report does not contain
- one completeness predicate, report_finishes(report, date), used by the live
  gate, the abandoned-date sweep and the pin sweep, so a report rejected by
  the block gate cannot be accepted by a recovery path
@STRML
STRML merged commit afe3147 into main Oct 27, 2026
2 checks passed
@STRML
STRML deleted the fix/triage-prompt-questions-only branch October 27, 2026 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant