Files
AgentHub/server/__tests__/env-scrub.test.js
T
nntrivi2001 478418f602 fix(macos): scrub parent-session env, survive node upgrades and reboots, store lanes by real path (#22)
What broke (code audit, untested on a Mac):
- Without systemd the server is always spawned from inside a Claude Code
  session, so it inherited CLAUDECODE, CLAUDE_CODE_SESSION_ID,
  CLAUDE_PLUGIN_ROOT, ... and tmux handed them to every lane pane (a codex
  pane even resolved as `claude` in `agenthub`'s harness check).
- The ~/.local/bin/agenthub launcher pinned a versioned node path that
  `brew upgrade`/nvm deletes, leaving the CLI dead until the next bootstrap.
- A discovery entry left over from before a reboot could name a recycled pid,
  so the bootstrap's fast path believed a dashboard was live and
  stopDashboard could signal an unrelated process.
- Lanes were stored under the typed cwd; macOS reports /private/tmp and the
  on-disk case, so sessions never bound to a /tmp or differently-cased lane.

What changed:
- server/index.js deletes a DENYLIST of parent-session variables at startup;
  user config (CLAUDE_CONFIG_DIR, CLAUDE_CODE_USE_BEDROCK, ANTHROPIC_*) stays.
- linkCli's launcher falls back to `node` on PATH when the pinned one is gone.
- liveServers ignores entries whose startedAt predates this boot (60 s margin).
- createLane and POST /api/lanes/ensure store/look up fs.realpathSync.native
  of an existing cwd; the liveness reaper also tries the realpath. Existing
  rows are untouched.
- session-liveness gains an exec seam so the darwin ps+lsof branch is tested.
- plugin-doctor names ~/.zshrc on darwin; INSTALL.md gains a macOS section
  with the Mac verification checklist, mirrored in both READMEs and PLUGINS.md.
No launchd, no macOptionIsMeta, engines stays >=20.
2026-09-25 11:23:59 +07:00

52 lines
1.9 KiB
JavaScript

/**
* @file Tests that the server drops the variables describing the Claude Code
* session that started it (always the case on macOS, where there is no
* systemd unit to give it a clean env) — tmux and every lane pane inherit the
* server's env, so a leaked CLAUDECODE made each pane look like a nested
* session. User configuration (CLAUDE_CONFIG_DIR, ANTHROPIC_*, …) must survive.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const { it, after } = require("node:test");
const assert = require("node:assert/strict");
const fs = require("fs");
const os = require("os");
const path = require("path");
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-envscrub-"));
process.env.DASHBOARD_DB_PATH = path.join(TMP, "dashboard.db");
process.env.CLAUDE_HOME = path.join(TMP, "home");
process.env.DASHBOARD_DATA_DIR = path.join(TMP, "data");
process.env.CLAUDECODE = "1";
process.env.CLAUDE_CODE_ENTRYPOINT = "cli";
process.env.CLAUDE_CODE_SESSION_ID = "abc";
process.env.CLAUDE_CODE_MESSAGING_SOCKET = "/tmp/x.sock";
process.env.CLAUDE_PLUGIN_ROOT = "/some/cache/dir";
process.env.CLAUDE_CONFIG_DIR = "/Users/x/.claude-work";
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.ANTHROPIC_BASE_URL = "https://example.invalid";
require("../index");
const { db } = require("../db");
after(() => {
db.close();
fs.rmSync(TMP, { recursive: true, force: true });
});
it("strips the parent session's variables and keeps user configuration", () => {
for (const k of [
"CLAUDECODE",
"CLAUDE_CODE_ENTRYPOINT",
"CLAUDE_CODE_SESSION_ID",
"CLAUDE_CODE_MESSAGING_SOCKET",
"CLAUDE_PLUGIN_ROOT",
]) {
assert.equal(process.env[k], undefined, k);
}
assert.equal(process.env.CLAUDE_CONFIG_DIR, "/Users/x/.claude-work");
assert.equal(process.env.CLAUDE_CODE_USE_BEDROCK, "1");
assert.equal(process.env.ANTHROPIC_BASE_URL, "https://example.invalid");
});