Skip to content

feat(box): give one agent variables of its own with --env - #199

Draft
Stener1 wants to merge 1 commit into
cosscom:mainfrom
Stener1:bra-2631-per-session-env
Draft

Stener1 wants to merge 1 commit into
cosscom:mainfrom
Stener1:bra-2631-per-session-env

Conversation

@Stener1

@Stener1 Stener1 commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

berthd sets env per box (~/.berth/env.json) and per repo (env in .berth/config.json), but not per session. This adds the third level: one task or session gets variables of its own, e.g. a ticket id for its hooks, a second Claude Code account (CLAUDE_CONFIG_DIR), or a different config for each agent you start.

berthd task new acme/fix-login --agent claude --prompt "fix the redirect" --env TICKET=T-7
berthd session new acme --agent claude --env CLAUDE_CONFIG_DIR=$HOME/.claude-second
# MCP: berth_task_new {..., "env": {"TICKET": "T-7"}}
 POST /v1/tasks, POST /v1/sessions       (+ env: {KEY: VALUE})
+  CheckSessionEnv(env)                  # before any worktree or session is made
   createAgentSessionWith(…, own)
     sessionEnv(dir, names(own))         # worktree env without those names (and no secret ref for them)
+    + own's KEY=VALUE                   # session > repo > box
+    @berth_env = "KEY1,KEY2"            # names only; values live in the tmux session env
 respawnAgent (terminal <-> chat switch, resume in place)
-  re-passes the worktree's value for every key
+  leaves own's names out (worktree value and the browser-timeout default), so tmux supplies them
  • Refused, with a message, nothing started: names berthd sets itself (BERTH_SESSION, BERTH_AGENT, BERTH_DRIVER, BERTH_PORT, BERTH_PORT_<n>, the worktree's BERTH_*, the browser-proxy vars, BERTH_SECRET_VARS), plus BERTH_HOME, BERTH_USER_DIR, BERTH_OP (the secret wrapper in the session trusts them to pick op and the 1Password session file). Malformed input is refused too: --env KEY with no =, an empty key, the same key twice, a non-identifier, or a NUL in a value. Other names, BERTH_ ones included, are the session's to use. A test fails if berthd starts setting a BERTH_ variable that a session may still set.
  • Values are taken literally: no $VAR expansion, no secret-reference resolution. The docs point secrets to env references.
  • Docs: reference/berthd.mdx, reference/cli.mdx, reference/files.mdx (new "A session's own variables" section), guides/runs.mdx (MCP tool), guides/project-config.mdx, changelog under Unreleased. The berth skill mentions --env. verify-berthd gets features/session-env.md, scripts/session-env-live.sh and scenarios/session-env-live.mjs.

Evidence

  • Before (v0.3.31): berthd task new acme/x --env A=b → usage: task new LOC/NAME [--agent ID] … [-- COMMAND...] (no such flag).
    After: on a dev box of this branch (dev-box.sh up), scripts/session-env-live.sh sets ACME_* in the box env (ACCOUNT, TICKET, REGION=box), the repo env (TICKET, REGION=repo) and each session's own (REGION, RUN). It then starts real Claude Code four ways, and each agent's own Bash tool ran env | grep ^ACME_:

    started with driver Bash tool saw
    task new --env structured (agent host) ACME_ACCOUNT=box ACME_REGION=eu-north ACME_RUN=task run 41 ACME_TICKET=repo
    session new --env structured ACME_ACCOUNT=box ACME_REGION=us-east ACME_RUN=session-42 ACME_TICKET=repo
    berth_task_new env:{…} (via berthd mcp) structured ACME_ACCOUNT=box ACME_REGION=ap-south ACME_RUN=mcp-43 ACME_TICKET=repo
    session new --env -- claude … terminal ACME_ACCOUNT=box ACME_REGION=eu-west ACME_RUN=term-44 ACME_TICKET=repo

    Refusals on the same box, with no session or worktree made:

    berthd: BERTH_SESSION is set by berthd for every session and can't be set for one
    berthd: invalid value "ACME_REGION" for flag -env: --env takes KEY=VALUE, got "ACME_REGION" with no =
    

    A live switch of the MCP-started session to its terminal and back (POST sessions/{name}/switch) kept ACME_REGION=ap-south ACME_RUN=mcp-43 in the new pane each time (switch.txt).

    The chats in the app (drive.mjs session-env-live --agent …, no console errors):

    task new --env: Claude's Bash output in the chat
    berth_task_new env: Claude's Bash output in the chat

  • Tests:

    • TestASessionsOwnEnvironmentComesOverTheProjectsAndTheBoxs: session > repo > box, for task and session, over a real tmux.
    • TestASessionCantSetBerthdsOwnVariablesOrMalformedOnes
    • TestEveryVariableBerthdSetsIsRefusedForASession: the drift guard.
    • TestAStructuredAgentKeepsItsOwnVariablesAcrossASwitch: the pane env from /proc after respawnAgent, including a session-set AGENT_BROWSER_IDLE_TIMEOUT_MS=0. It fails without the respawn fix.
    • TestEnvFlagsSetTheNewSessionsOwnVariables, TestAMalformedEnvFlagIsRefusedNeverDropped, TestATasksAgentGetsTheVariablesItWasGiven (MCP).
  • Checks:

    • go vet ./... is clean.
    • go test -race ./internal/box ./internal/boxcmd ./internal/mcpserver passes on the final commit.
    • The full go test -race ./... passed except internal/agent's two SSH-route timeouts (they pass 3/3 on rerun) and leadline/api's TestDetach, which fails on main on this machine too.
    • The app's pnpm build and the docs-site build pass.
    • Not run here: cargo check, because this Linux box has no GTK dev libs. No Rust changed.

Merge Danger

Door: two-way

Additive: with no env, every path builds the same environment as before (TestSessionsWithoutSecretsStartAsTheyAlwaysDid still asserts the exact new-session args). Revert is a plain revert. Sessions started with --env just keep an unused @berth_env option.

Blast Radius: sessions

Every agent and terminal start goes through the changed createAgentSessionWith/sessionEnv, and every terminal↔chat switch and resume-in-place through respawnAgent. A mistake there would show up as a wrong or missing variable in new sessions, not as data loss.

🤖 Generated with Claude Code

View guided diff

berthd task new and session new take --env KEY=VALUE (repeatable), the
task and session API take env, and the berth_task_new MCP tool takes an
env object. The variables reach that session alone, in its terminal or
through the agent host, over the same names from the box's env.json and
the project's env, and they last through a switch between the terminal
and the chat. Names berthd sets itself (BERTH_SESSION, BERTH_PORT, the
worktree's BERTH_*, BERTH_HOME, BERTH_OP, …) and malformed ones are
refused before anything is made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 28, 2026

Copy link
Copy Markdown

@Stener1 is attempting to deploy a commit to the seanbrydonio's projects Team on Vercel.

A member of the Team first needs to authorize it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant