Skip to content
ai-sandbox-local
Esc
↑↓navigate↵open⌘Jpreview
On this page

Working directory and $HOME

Where the agent works, where the harness keeps its own files, and how to move them.

Two directories matter, and they are set in two different places. workingDirectory and sandboxConfig.workDir decide where the agent works. $HOME decides where the harness keeps its own files.

Where the agent works

createLocalSandboxSession({ workingDirectory }) resolves the path and creates it with mkdir -p if it is missing. That directory becomes the sandbox’s defaultWorkingDirectory.

What the agent sees is set on the HarnessAgent, with sandboxConfig.workDir, which is relative to defaultWorkingDirectory:

workDir The agent works in
'.' workingDirectory itself. This is what you want for a repo.
omitted <workingDirectory>/claude-code-<sessionId>, a fresh empty subdirectory. Your repo is its parent, not its working directory.
'packages/app' <workingDirectory>/packages/app, created if missing.

The omitted case is the default, and the trap: the agent starts without error and finds an empty directory. Always pass workDir: '.' when the agent should see your files.

workDir must be relative and stay inside workingDirectory: HarnessAgent throws on an absolute path or one that starts with ...

Where harness state goes

The harness keeps its own files outside the directory the agent works in, under $HOME/.ai-sdk-harness of the sandbox. By default the sandbox’s $HOME is not your real home: it is ai-sandbox-local/home under the OS temp directory (os.tmpdir()), created if missing.

  • /tmp/ai-sandbox-local/home/ (the sandbox’s $HOME)
    • .ai-sdk-harness/
      • .harness-bootstrap/
        • claude-code/
      • .agent-runs/
  • your-repo/

.harness-bootstrap/claude-code holds the pinned CLI install and its bridge, and .agent-runs holds per-session bridge state. Your real home and your repo stay clean, and the CLI install, which runs pnpm install, never runs inside your own pnpm workspace.

The directory is shared by every session and working directory on purpose, so the roughly 20-second first-run CLI install happens once. The OS may clear its temp directory (macOS does after a few days, and Linux often on reboot). That only means a slow first run again.

The harness finds $HOME by running printf "$HOME" inside the sandbox, so the HOME the sandbox gives its processes is what decides all of this.

Choose it with homeDirectory

Pass homeDirectory to put the state somewhere else, such as persistent storage you control, or your real home:

import { homedir } from 'node:os';

const sandboxSession = await createLocalSandboxSession({
	workingDirectory: '/path/to/your/repo',
	homeDirectory: '/path/to/state', // or homedir() to use your real home
});

homeDirectory is resolved to an absolute path and created with mkdir -p. env.HOME also works and wins over homeDirectory; see Configuration for the full precedence.

What a different $HOME hides

Everything the agent runs sees this $HOME, not only the harness. Anything that reads config from HOME will not find your dotfiles under the default:

  • Git identity and settings in ~/.gitconfig, so commits fail or use a wrong author.
  • SSH keys and known_hosts in ~/.ssh.
  • npm, gh, cloud CLI and other tool logins stored under ~.
  • Claude Code’s own ~/.claude and ~/.claude.json, so the session doesn’t see your user-level Claude settings.

Credentials passed through environment variables, such as ANTHROPIC_API_KEY, keep working. To get your dotfiles back, pass homeDirectory: homedir(), which puts the harness state in your real home. To keep the default and expose only what you need, pass specific variables through env, for example GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, or GIT_CONFIG_GLOBAL pointing at your ~/.gitconfig.

CLI logins still work

The Codex and OpenCode adapters read your existing CLI login on the host, from $CODEX_HOME or ~/.codex/auth.json for Codex and from ~/.local/share/opencode/auth.json (or OPENCODE_AUTH_CONTENT) for OpenCode, then forward it into the sandbox as environment variables, so the temp-dir HOME doesn’t break them. An API key in the host environment takes precedence over the stored login. OpenCode picks the stored login from its provider setting, not from the model id, so for a non-Anthropic model pass it explicitly, as in createOpenCode({ provider: 'xai' }) with model: 'xai/grok-4.3'. Without that, it looks for an Anthropic login and the turn fails with “Model not found”. The OpenCode adapter runs OpenCode v1, which reads the v1 auth file, so a login made only with OpenCode v2 isn’t seen.

Was this page helpful?