Fix env var: CLAUDE_CONFIG_DIR not CLAUDE_HOME

Foreman verified by 'strings' on the claude binary that the
sandbox-isolation env var is CLAUDE_CONFIG_DIR, not CLAUDE_HOME
(2026-05-06 sandbox spike).

identity.ts now resolves in this order:
  1. PING_AGENT_IDENTITY
  2. $CLAUDE_CONFIG_DIR/ping-agent  (preferred — what Claude Code reads)
  3. $CLAUDE_HOME/ping-agent         (compat for the previously-documented var)
  4. ~/.ping-agent

Two new tests cover the precedence (CLAUDE_CONFIG_DIR > CLAUDE_HOME)
and the fallback path (CLAUDE_HOME still works when CLAUDE_CONFIG_DIR
is unset). Existing tests updated to use CLAUDE_CONFIG_DIR. 37 tests
pass.

README + install.sh: sandbox launch examples updated to set
CLAUDE_CONFIG_DIR.

Note: agent-ping's identity-resolution PR (#1, merged) has the same
bug and should also be patched. Filing a follow-up.
This commit is contained in:
bob 2026-05-06 18:05:22 -03:00
parent c22558c67a
commit c757af3909
4 changed files with 50 additions and 17 deletions

View file

@ -23,20 +23,49 @@ describe("resolveIdentity", () => {
expect(r.source).toMatch(/env/i);
});
it("uses $CLAUDE_HOME/ping-agent next", () => {
it("uses $CLAUDE_CONFIG_DIR/ping-agent next", () => {
writeFileSync(join(dir, ".ping-agent"), "default-agent\n");
const claudeHome = join(dir, "claude-sandbox");
const sandbox = join(dir, "claude-sandbox");
mkdirSync(sandbox);
writeFileSync(join(sandbox, "ping-agent"), "sandbox\n");
const r = resolveIdentity({
HOME: dir,
CLAUDE_CONFIG_DIR: sandbox,
PING_AGENT_IDENTITY: "",
});
expect(r.agent).toBe("sandbox");
expect(r.source).toContain(sandbox);
});
it("falls back to $CLAUDE_HOME/ping-agent when CLAUDE_CONFIG_DIR is unset", () => {
writeFileSync(join(dir, ".ping-agent"), "default-agent\n");
const claudeHome = join(dir, "claude-old");
mkdirSync(claudeHome);
writeFileSync(join(claudeHome, "ping-agent"), "sandbox\n");
writeFileSync(join(claudeHome, "ping-agent"), "compat\n");
const r = resolveIdentity({
HOME: dir,
CLAUDE_HOME: claudeHome,
PING_AGENT_IDENTITY: "",
});
expect(r.agent).toBe("sandbox");
expect(r.agent).toBe("compat");
expect(r.source).toContain(claudeHome);
});
it("CLAUDE_CONFIG_DIR takes precedence over CLAUDE_HOME", () => {
const sandbox = join(dir, "claude-sandbox");
const old = join(dir, "claude-old");
mkdirSync(sandbox);
mkdirSync(old);
writeFileSync(join(sandbox, "ping-agent"), "new\n");
writeFileSync(join(old, "ping-agent"), "old\n");
const r = resolveIdentity({
HOME: dir,
CLAUDE_CONFIG_DIR: sandbox,
CLAUDE_HOME: old,
});
expect(r.agent).toBe("new");
});
it("falls through to ~/.ping-agent", () => {
writeFileSync(join(dir, ".ping-agent"), "bob\n");
const r = resolveIdentity({ HOME: dir });
@ -48,12 +77,12 @@ describe("resolveIdentity", () => {
expect(() => resolveIdentity({ HOME: dir })).toThrow(/no identity/);
});
it("ignores empty CLAUDE_HOME/ping-agent and falls through", () => {
it("ignores empty CLAUDE_CONFIG_DIR/ping-agent and falls through", () => {
writeFileSync(join(dir, ".ping-agent"), "bob\n");
const claudeHome = join(dir, "claude-sandbox");
mkdirSync(claudeHome);
writeFileSync(join(claudeHome, "ping-agent"), "");
const r = resolveIdentity({ HOME: dir, CLAUDE_HOME: claudeHome });
const sandbox = join(dir, "claude-sandbox");
mkdirSync(sandbox);
writeFileSync(join(sandbox, "ping-agent"), "");
const r = resolveIdentity({ HOME: dir, CLAUDE_CONFIG_DIR: sandbox });
expect(r.agent).toBe("bob");
});