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
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:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user