A session id reaching the Stop and SessionStart hooks was only checked for blank, so "../../x" or "a/b" could read or write outside the waits directory; both hooks now hold every session id to waitSessionID before building any path, and allow (saying why) rather than fail on a bad one. claude_code_wait's list/watched/done/said held only add to that same check, letting a malformed id through as an ordinary, always-empty key; they all refuse it now. The Stop hook's three-strikes cap could be bypassed forever if its counter file could not be written on a refusal: it now allows instead, saying so, so a wait can never hold a turn open indefinitely. Its advice offered mesh_wait on a wait's event family unconditionally, ignoring that the session's tool runner may not be granted it (ADR 0367 §2); it now offers mesh_wait only "if this session's tool runner is granted it, otherwise start a watcher", and drops the family entirely when a wait has none. Both hooks' fail-open and let-through notices were said only on stderr, which Claude Code does not show the operator on an allowed (exit 0) outcome; they now also print the same words as a "systemMessage" JSON object on stdout, which Claude Code does show.
24 lines
850 B
Go
24 lines
850 B
Go
package main
|
|
|
|
// Shared by the plugin's hooks (novox/hq ADR 0489 §5): a notice on exit 0 — the stop hook failing open or
|
|
// letting a wait through, the session-start hook unable to use a session id — reaches the operator's screen,
|
|
// not only the log. Claude Code shows a hook's top-level "systemMessage" field to the person; stderr alone,
|
|
// on an allowed (exit 0) outcome, is never shown there.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// writeSystemMessage prints message as the one JSON object Claude Code shows the operator, falling back to
|
|
// the plain words on the one error that cannot happen (a string always marshals).
|
|
func writeSystemMessage(w io.Writer, message string) {
|
|
raw, err := json.Marshal(map[string]string{"systemMessage": message})
|
|
if err != nil {
|
|
fmt.Fprintln(w, message)
|
|
return
|
|
}
|
|
fmt.Fprintln(w, string(raw))
|
|
}
|