diff --git a/modules/claude-code/cmd/claude-code/proposals.go b/modules/claude-code/cmd/claude-code/proposals.go index f46cddde..78fc06ce 100644 --- a/modules/claude-code/cmd/claude-code/proposals.go +++ b/modules/claude-code/cmd/claude-code/proposals.go @@ -838,14 +838,20 @@ func HeardEnd(p Paths, store ProposalStore, w asks.Warrant, now time.Time) (stri if !known || (w.About != "" && w.About != prop.Ask().About) { return "", nil } - endedMu.Lock() - ended[w.Ask] = w - endedMu.Unlock() + // Said to a proposer waiting on it only once the discard is kept: what it reads next is already there (the + // flake of the router's refusal test under a loaded build agent, 2026-10-12). + tell := func() { + endedMu.Lock() + ended[w.Ask] = w + endedMu.Unlock() + } approved, err := ReadApproved(p) if err != nil { + tell() return "", err } if _, done := approved.Decided[w.Ask]; done { + tell() return "", nil } // Heard, not read from a record: Reconcile still reads the router's record of it, so an ask the router took after @@ -856,7 +862,9 @@ func HeardEnd(p Paths, store ProposalStore, w asks.Warrant, now time.Time) (stri } approved.Decided[w.Ask] = d delete(approved.Refused, w.Ask) - if err := writeApproved(p, approved); err != nil { + err = writeApproved(p, approved) + tell() + if err != nil { return "", err } return "proposal " + w.Ask + ": " + d.Outcome, nil