What happened
On a fresh Claude Code 2.1.278 session with plugin 0.21.0, the TUI showed a red startup error on every screen:
SessionStart:startup hook error
Hook output looks like a JSON object but is not valid JSON — JSON Parse error: Unable to parse JSON string. Emit the payload with a JSON encoder (jq, ConvertTo-Json, json.dumps) rather than string concatenation so backslashes and quotes inside strings are escaped.
The hook's raw stdout for that SessionStart, from the session transcript, was two JSON documents back to back:
{"systemMessage":"[ACP] 7 tool calls recorded while ACP could not see them (offline, or before this key was connected) are uploading once now; they will appear in your audit with their original timestamps."}{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"[ACP] Governance plugin claude-code-plugin v0.21.0 is outdated — v0.25.0 is current. ..."}}
Each write on its own is valid. Concatenated they are not, and Claude Code parses the hook's whole stdout as one document.
When it happens
Any SessionStart where more than one notice applies in the same run of govern.mjs: here the offline-ledger flush notice (0.16.0) plus the stale-plugin notice (0.21.0). Other pairs of process.stdout.write(JSON.stringify(...)) on the same event would do the same.
Effect
The error is cosmetic for enforcement (PreToolUse still ran and denied the force-push in the same session), but both notices are lost, the session shows a red hook error on every screen, and it is the first thing a new user sees after install.
Fix shape
One stdout write per hook invocation: collect notices, then emit a single object with systemMessage and hookSpecificOutput together (Claude Code accepts both fields in one document). A guard in the test suite that a hook run never writes to stdout twice would catch the next pair.
Seen 2026-09-21 in session 15895bcc-45b6-4cfe-8372-4859beb255be (tenant dcroweua). Not yet checked whether 0.25.0 still has both writes.
What happened
On a fresh Claude Code 2.1.278 session with plugin 0.21.0, the TUI showed a red startup error on every screen:
The hook's raw stdout for that SessionStart, from the session transcript, was two JSON documents back to back:
Each write on its own is valid. Concatenated they are not, and Claude Code parses the hook's whole stdout as one document.
When it happens
Any SessionStart where more than one notice applies in the same run of
govern.mjs: here the offline-ledger flush notice (0.16.0) plus the stale-plugin notice (0.21.0). Other pairs ofprocess.stdout.write(JSON.stringify(...))on the same event would do the same.Effect
The error is cosmetic for enforcement (PreToolUse still ran and denied the force-push in the same session), but both notices are lost, the session shows a red hook error on every screen, and it is the first thing a new user sees after install.
Fix shape
One stdout write per hook invocation: collect notices, then emit a single object with
systemMessageandhookSpecificOutputtogether (Claude Code accepts both fields in one document). A guard in the test suite that a hook run never writes to stdout twice would catch the next pair.Seen 2026-09-21 in session
15895bcc-45b6-4cfe-8372-4859beb255be(tenant dcroweua). Not yet checked whether 0.25.0 still has both writes.