package main // The plugin's SessionStart hook (novox/hq ADR 0489 ยง1, issue 1744): `claude-code session-start `, // run by the agent's own account on every start, resume, /clear and /compact. The tool runner never tells a // session its own id (only a hook's stdin carries session_id), so a session cannot name itself to // claude_code_wait until this hook has told it. It also lists the session's still-open waits, so a session // that /clear-ed sees what it was watching before. // // It reads the session's mirrored waits file directly โ€” the same local read the Stop hook makes // (readWaitsFile, waits.go) โ€” never calling back into this module: this hook runs on every start, and must // not wait on the bus to do it. A missing or malformed file is not an error here: there is simply nothing // open to report. import ( "encoding/json" "fmt" "io" "strings" "time" ) // SessionStartInput is what the hook is given on stdin. type SessionStartInput struct { SessionID string `json:"session_id"` } // SessionStartMessage is the line the hook prints: the session's id and how to give it to claude_code_wait, // then each still-open wait from before (1744/AC1, AC2). Pure of IO beyond the one file read. func SessionStartMessage(sessionID, waitsDir string, now time.Time) string { var b strings.Builder fmt.Fprintf(&b, "This session's id is %s; give it as `session` to claude_code_wait.\n", sessionID) f, err := readWaitsFile(waitsFileIn(waitsDir, sessionID)) if err != nil || len(f.Waits) == 0 { return b.String() } b.WriteString("Waits still open from before this session's last start, resume, /clear or /compact:\n") for _, w := range f.Waits { age := now.Sub(w.At).Round(time.Second) state := "no watcher yet" if w.Watcher != "" { state = "watched" } if w.PastBound(now) { state += ", past its bound" if w.Said { state += ", said" } } fmt.Fprintf(&b, " %s โ€” %s (%s ago, %s)\n", w.ID, w.What, age, state) } return b.String() } // runSessionStart is the hook: the session's id and its open waits out, exit 0 always (1744/AC3). func runSessionStart(waitsDir string, stdin io.Reader, stdout io.Writer) (code int) { defer func() { if recover() != nil { code = 0 // never fails the session's start } }() var in SessionStartInput if err := json.NewDecoder(stdin).Decode(&in); err != nil || strings.TrimSpace(in.SessionID) == "" { return 0 } fmt.Fprint(stdout, SessionStartMessage(in.SessionID, waitsDir, time.Now())) return 0 }