Route adopt's refusal-counter reset through fsys, and name the wait in the Stop hook's suggestions (issues 1749, 1757)

issue 1749: AdoptName reset both session ids' refusal-counter files with
os.WriteFile, bypassing fsys; on a node whose agent has its own account
(ADR 0266) the module runs as the operator's account and cannot write
into the agent's home directly, so the reset silently failed. writeRefusalCounters
now goes through fsys (MkdirAll then WriteFile), as every other write into
the agent's home does; it is also called from the Stop hook itself, which
already runs as the agent's own account, where fsys is its zero value and
behaves exactly as the plain write did before.

issue 1757: the Stop hook's suggested watched and said calls left out `by`,
which claude_code_wait requires on every op, so a session following the
suggestion word for word was refused. Both now carry "by" from the wait's
own By field (already kept per wait since issue 1629). The SessionStart
hook's suggested adopt call already carried by; added a test for it too,
alongside the Stop hook's two, each extracting the exact JSON the hook
prints and feeding it back to the tool's own dispatch to prove it is
accepted as printed.
This commit is contained in:
jochen
2026-10-21 19:35:33 +02:00
parent c3564c1acc
commit 08cec8cb1c
3 changed files with 181 additions and 7 deletions
@@ -273,3 +273,31 @@ func TestHookRegisterStillRefusesEveryHook(t *testing.T) {
t.Fatalf("a Stop hook was not refused: %v %s", isError, text)
}
}
// Issue 1749: adopt's refusal-counter reset writes into the agent's home (beside the waits file) and must
// go through fsys like every other such write (novox/hq ADR 0266) — on a confined node the module runs as
// the operator's account, which cannot write there directly; only the helper, as the agent's own account,
// can. The confined-node fake (agentFiles, agent_account_test.go) proves it: a write that reaches the real
// filesystem only through the helper's request/response protocol bumps its count.
func TestWriteRefusalCountersGoesThroughTheHelper(t *testing.T) {
f, home, counted := agentFiles(t)
was := fsys
fsys = f
t.Cleanup(func() { fsys = was })
waitsDir := filepath.Join(home, ".claude", "nox-mesh", "waits")
if err := writeRefusalCounters(waitsDir, "sess-1", map[string]int{"w-1": 2}); err != nil {
t.Fatal(err)
}
if counted.asked.Load() == 0 {
t.Fatal("the counter file was written directly, never through the helper")
}
raw, err := os.ReadFile(filepath.Join(waitsDir, "sess-1.refusals.json"))
if err != nil {
t.Fatal(err)
}
var m map[string]int
if json.Unmarshal(raw, &m) != nil || m["w-1"] != 2 {
t.Fatalf("the written counters: %s", raw)
}
}