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.
94 lines
3 KiB
TypeScript
94 lines
3 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { resolveIdentity } from "../src/identity.js";
|
|
|
|
let dir: string;
|
|
beforeEach(() => {
|
|
dir = mkdtempSync(join(tmpdir(), "watcher-id-"));
|
|
});
|
|
afterEach(() => {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe("resolveIdentity", () => {
|
|
it("uses PING_AGENT_IDENTITY env var first", () => {
|
|
writeFileSync(join(dir, ".ping-agent"), "default-agent\n");
|
|
const r = resolveIdentity({
|
|
HOME: dir,
|
|
PING_AGENT_IDENTITY: "envname",
|
|
});
|
|
expect(r.agent).toBe("envname");
|
|
expect(r.source).toMatch(/env/i);
|
|
});
|
|
|
|
it("uses $CLAUDE_CONFIG_DIR/ping-agent next", () => {
|
|
writeFileSync(join(dir, ".ping-agent"), "default-agent\n");
|
|
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"), "compat\n");
|
|
const r = resolveIdentity({
|
|
HOME: dir,
|
|
CLAUDE_HOME: claudeHome,
|
|
PING_AGENT_IDENTITY: "",
|
|
});
|
|
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 });
|
|
expect(r.agent).toBe("bob");
|
|
expect(r.source).toContain(".ping-agent");
|
|
});
|
|
|
|
it("throws when nothing resolves", () => {
|
|
expect(() => resolveIdentity({ HOME: dir })).toThrow(/no identity/);
|
|
});
|
|
|
|
it("ignores empty CLAUDE_CONFIG_DIR/ping-agent and falls through", () => {
|
|
writeFileSync(join(dir, ".ping-agent"), "bob\n");
|
|
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");
|
|
});
|
|
|
|
it("trims whitespace from identity files", () => {
|
|
writeFileSync(join(dir, ".ping-agent"), " bob \n\n");
|
|
const r = resolveIdentity({ HOME: dir });
|
|
expect(r.agent).toBe("bob");
|
|
});
|
|
});
|