package main // The agent's session from the operator's shell (novox/hq ADR 0266): where a machine names an account of the // agents' own, `claude` typed in the operator's interactive zsh is `claude-agent`; where agents run as the // operator's account, nothing is added. The manifest writes the account agents run as to a file, and the zsh // code it contributes reads it: the controller never fills a machine fact into shell code. import ( "encoding/json" "os" "os/exec" "os/user" "path/filepath" "strings" "testing" ) const agentAccountFile = "/etc/claude-code/agent-account" type manifestShell struct { Resources []map[string]any `json:"resources"` // The zsh code is an item of the collection shell:zsh: (novox/hq ADR 0349 clause 13), its text the // template file beside the manifest. Items []struct { Collection string `json:"collection"` Template string `json:"template"` } `json:"items"` } func readManifestShell(t *testing.T) manifestShell { t.Helper() raw, err := os.ReadFile("../../module.json") if err != nil { t.Fatal(err) } var m manifestShell if err := json.Unmarshal(raw, &m); err != nil { t.Fatal(err) } return m } func resourceByID(t *testing.T, m manifestShell, id string) map[string]any { t.Helper() for _, r := range m.Resources { if r["id"] == id { return r } } t.Fatalf("the manifest has no resource %q", id) return nil } // zshCode is the one piece of zsh code the module contributes. func zshCode(t *testing.T, m manifestShell) string { t.Helper() var code []string for _, it := range m.Items { if strings.HasPrefix(it.Collection, "shell:zsh:") { text, err := os.ReadFile(filepath.Join("..", "..", it.Template)) if err != nil { t.Fatal(err) } code = append(code, string(text)) } } if len(code) != 1 { t.Fatalf("the module contributes %d pieces of zsh code; one is expected", len(code)) } return code[0] } // The file names the account agents run as, from the machine fact, which is the operator's where none is named. func TestTheAccountAgentsRunAsIsWrittenForTheShell(t *testing.T) { r := resourceByID(t, readManifestShell(t), "agent-account-name") if r["type"] != "file" || r["path"] != agentAccountFile || r["mode"] != "0644" { t.Fatalf("the account's file is %v", r) } if text := templateText(t, r); text != "${machine:agent-account}\n" { t.Fatalf("the account's file holds %q", text) } if _, has := r["owner"]; has { t.Fatal("the account's file must be root's, as claude-agent is") } } // The alias stands once, and only inside the guard that compares the account with the shell's own. func TestTheAliasStandsOnlyBehindTheGuard(t *testing.T) { code := zshCode(t, readManifestShell(t)) if n := strings.Count(code, "alias claude="); n != 1 { t.Fatalf("the code names the alias %d times", n) } lines := strings.Split(code, "\n") guard, alias, end := -1, -1, -1 for i, l := range lines { l = strings.TrimSpace(l) switch { case l == "if [[ -n $agent && $agent != $USERNAME ]]; then": guard = i case l == "alias claude=claude-agent": alias = i case l == "fi" && guard >= 0 && alias > guard && end < 0: end = i } } if guard < 0 || alias < 0 || end < 0 || !(guard < alias && alias < end) { t.Fatalf("the alias is not inside the guard:\n%s", code) } if strings.Contains(code, "${") { t.Fatal("the code holds ${…}, which the controller does not fill in shell code and zsh reads as its own") } } // Run in zsh, the code adds the alias only where the file names an account other than the shell's. func TestTheCodeAddsTheAliasOnlyForAnotherAccount(t *testing.T) { zsh, err := exec.LookPath("zsh") if err != nil { t.Skip("no zsh here") } me, err := user.Current() if err != nil { t.Fatal(err) } code := zshCode(t, readManifestShell(t)) cases := []struct { name string content *string alias bool }{ {"an agent account of its own", ptr("agent-of-its-own\n"), true}, {"agents run as the operator's account", ptr(me.Username + "\n"), false}, {"an empty file", ptr(""), false}, {"no file", nil, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { file := filepath.Join(t.TempDir(), "agent-account") if c.content != nil { if err := os.WriteFile(file, []byte(*c.content), 0o644); err != nil { t.Fatal(err) } } script := strings.ReplaceAll(code, agentAccountFile, file) + "alias claude || true\n" out, err := exec.Command(zsh, "-f", "-c", script).CombinedOutput() if err != nil { t.Fatalf("%v: %s", err, out) } got := strings.TrimSpace(string(out)) if c.alias && got != "claude=claude-agent" { t.Fatalf("no alias: %q", got) } if !c.alias && got != "" { t.Fatalf("an alias, or output, where none belongs: %q", got) } }) } } // claude-agent runs the real claude, never itself: with the alias in force in the shell that types claude, // claude-agent's own `exec claude` finds the program on PATH, since an alias is a word of an interactive zsh // and neither exec, sudo nor claude-agent's sh sees it. func TestClaudeAgentRunsTheRealClaudeUnderTheAlias(t *testing.T) { zsh, err := exec.LookPath("zsh") if err != nil { t.Skip("no zsh here") } me, err := user.Current() if err != nil { t.Fatal(err) } start := templateText(t, resourceByID(t, readManifestShell(t), "start")) if !strings.Contains(start, "exec claude \"$@\"") { t.Fatalf("claude-agent no longer runs claude itself:\n%s", start) } bin := t.TempDir() // Rendered for an account that is the caller's own, the branch that runs claude without sudo. // A line after the shebang says the call went through it. agent := strings.ReplaceAll(start, "${machine:agent-account}", me.Username) agent = strings.Replace(agent, "\n", "\necho through claude-agent\n", 1) if err := os.WriteFile(filepath.Join(bin, "claude-agent"), []byte(agent), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(bin, "claude"), []byte("#!/bin/sh\necho \"the real claude $*\"\n"), 0o755); err != nil { t.Fatal(err) } // zsh reads a -c string whole before it runs any of it, so the typed line is read after the alias, by eval. cmd := exec.Command(zsh, "-f", "-c", "alias claude=claude-agent\neval 'claude --resume'\n") cmd.Env = append(os.Environ(), "PATH="+bin+string(os.PathListSeparator)+os.Getenv("PATH")) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("%v: %s", err, out) } if got := strings.TrimSpace(string(out)); got != "through claude-agent\nthe real claude --resume" { t.Fatalf("claude under the alias ran %q", got) } } func ptr(s string) *string { return &s }