claude-code: remove from or restore to only an agent account's own home
mesh/merge-gate pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh, merge-check-typescript.sh): main's version judged it; the change's judges the pull r…
mesh/delivery delivered

Removing a rule from the operator's own home changes what their sessions
obey as surely as placing one: an agent could remove the rule that
constrains it (hq issue 340).
This commit is contained in:
jochen
2026-10-09 09:51:18 +02:00
parent 3677af6079
commit d984eb84d6
5 changed files with 66 additions and 7 deletions
@@ -487,3 +487,40 @@ func TestAHomeItemOnTheBusIsNotPlacedInTheOperatorsHome(t *testing.T) {
t.Errorf("the render did not say it left the item out: %v", out)
}
}
// Removing an item of the operator's own home, or putting one back, changes what every session there obeys as
// surely as placing one: an agent could remove a rule that constrains it (novox/hq issue 340). Through the real
// tool dispatch, both are refused wherever the home is the operator's, and nothing in it changes; where the home
// is an agent account's own they are taken.
func TestTheOperatorsHomeIsNotChangedThroughTheTools(t *testing.T) {
p, _ := node(t, "laptop")
rule := filepath.Join(p.Home, ".claude", "rules", "constraint.md")
_ = os.MkdirAll(filepath.Dir(rule), 0o755)
writeFile(t, rule, "Never push to main.")
s := serve(t, p, &bus{kept: map[string]map[string]any{}}, memConfig{})
text, isError := s.call(t, "claude_code_home_remove", map[string]any{"kind": "instructions", "name": "constraint",
"why": "the person asked"})
if !isError || !strings.Contains(text, "issue 340") || !strings.Contains(text, "Nothing was changed") {
t.Errorf("removing the operator's rule was not refused: %s", text)
}
if raw, err := os.ReadFile(rule); err != nil || string(raw) != "Never push to main." {
t.Fatalf("the operator's rule changed: %q %v", raw, err)
}
text, isError = s.call(t, "claude_code_home_restore", map[string]any{"kept": "2026-10-09T00:00:00Z"})
if !isError || !strings.Contains(text, "issue 340") {
t.Errorf("putting back in the operator's home was not refused: %s", text)
}
agent, _ := confinedNode(t, "anchor")
own := filepath.Join(agent.Home, ".claude", "rules", "old.md")
_ = os.MkdirAll(filepath.Dir(own), 0o755)
writeFile(t, own, "x")
a := serve(t, agent, &bus{kept: map[string]map[string]any{}}, memConfig{})
if text, isError := a.call(t, "claude_code_home_remove", map[string]any{"kind": "instructions", "name": "old",
"why": "the person asked"}); isError {
t.Fatalf("removing from the agent account's own home was refused: %s", text)
}
if _, err := os.Stat(own); err == nil {
t.Fatal("the agent's own rule was not removed")
}
}