claude-cli emits hardcoded synthetic literal with no error propagation when PDF validation fails; persists across resume
Summary
When claude-cli encounters a PDF that cannot be converted to the API document content block (e.g., truncated PDF), it emits a hardcoded synthetic message to the user, recorded in the session .jsonl with model: "<synthetic>", input_tokens: 0, output_tokens: 0, stop_reason: "stop_sequence". No API call is made and no error is surfaced.
The exact literal emitted: "The PDF file was not valid. Try converting it to text first (e.g., pdftotext)." (78 bytes, sha256 02df1d51bd7e9f1c3b44b1e0e431b8704bb80a8f8fc10e286894a61a51042429).
Persistence on resume
Once recorded in .jsonl, resuming the session via --resume <session-id> continues to short-circuit on every subsequent user prompt, emitting the same byte-identical synthetic literal indefinitely. This persists through subprocess restart (when the harness preserves the same session id) and webchat UI refreshes. Only abandoning the resume token (starting a fresh session via --resume omission or equivalent) recovers.
Case study
2026-05-12 incident: 11 byte-identical synthetic responses over 12 minutes, 9 distinct user retries (including /doctor and explicit skill invocation) all received the same canned response. Full postmortem available on request.
Expected
Either:
- (a) Surface as a proper error/exception to the user/model, not as a fake assistant message with
model: "<synthetic>". The model should be able to see the failure and respond intelligently (e.g., suggest alternative handling, ask user, etc.). - (b) If a synthetic fallback is intentional, ensure it does NOT poison the resume state: subsequent prompts should be processed fresh, not replay the canned response.
Open question
Persistence mechanism not fully isolated. Two readings remain open:
- The truncated PDF reference in historical context gets re-validated on every resume and re-triggers the short-circuit
- The synthetic
stop_reason: "stop_sequence"marker puts claude-cli into a replay state for the last assistant text
Distinguishing requires a controlled experiment outside the incident's session.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗