CB-112: workers inherit the primary's working directory (not $HOME)

A worker now opens the same directory the primary is in, unless told otherwise.
Resolution: explicit spawn cwd → per-profile config cwd → the primary's cwd
(auto-detected from the bridge_spawn caller's PID via lsof -d cwd) → the daemon's
cwd. Never $HOME.

Mechanism (found by live probe, corrects the earlier assumption): an agent.start
pane does NOT inherit its tab's or workspace's cwd — it starts in $HOME. herdr's
agent.start honours an (undocumented) cwd param, so the resolved cwd is threaded
onto agent.start {cwd} (both tab and pane placement), not tab.create.

Surfaces: bridge_spawn {cwd?} + auto-detect via ConnectionIdentity.resolve (peer
PID) + ProcessCwdLookup (lsof); REST POST /workers ?cwd= / body cwd; per-profile
'cwd:' config. Validated live: explicit cwd → worker rooted there; no cwd over
REST → daemon cwd, not $HOME. Also clears the ccs folder-trust prompt when the
project dir is already trusted (see docs/Worker-Startup-and-Trust.md).
This commit is contained in:
Dai Ha
2026-07-15 16:33:46 +02:00
parent 959f04bc96
commit 926724a279
16 changed files with 311 additions and 69 deletions
+13 -10
View File
@@ -19,7 +19,7 @@ flowchart TD
B -->|"no"| D{"caller PID resolvable?<br/>(MCP peer PID)"}
D -->|"yes"| E["cwd = the primary's cwd<br/>lsof -a -p PID -d cwd"]
D -->|"no (REST / off-host)"| F["cwd = bridged daemon cwd<br/>(never $HOME by assumption)"]
C --> G["workspace.create {cwd} → tab.create → agent.start"]
C --> G["ensureWorkspace → tab.create → agent.start {cwd}"]
E --> G
F --> G
G --> H{"does the CLI trust this folder?"}
@@ -40,10 +40,11 @@ otherwise. Never assume `$HOME`.** If the primary is in `/Users/you/LTMS/claude-
open there too — so delegated tasks share the same relative paths and the same (already-trusted)
project folder.
**The herdr seam.** herdr fixes a session's working directory at **`workspace.create {cwd}`** —
`agent.start` takes `{name, argv, env, tab_id}` with **no** `cwd`. So the worker's cwd is whatever
its *workspace/tab* was created with; controlling it means threading a `cwd` into the placement step,
not the launch step.
**The herdr seam.** An `agent.start` pane does **not** inherit its tab's or workspace's cwd — it
starts in `$HOME` unless told otherwise. herdr's `agent.start` honours an (undocumented) **`cwd`**
param, verified live: setting it roots the worker process at that directory. So the worker's cwd is
threaded onto `agent.start {…, cwd}`, not the placement step (`workspace.create`/`tab.create` cwd
only affect the seed shell, which the bridge closes).
**Resolution order** (first match wins):
@@ -69,16 +70,18 @@ sequenceDiagram
O-->>B: "pid"
B->>O: "cwd of pid (lsof -d cwd)"
O-->>B: "/Users/you/LTMS/claude-bridge"
B->>H: "workspace.create {cwd} / tab.create"
B->>H: "agent.start {argv, env, tab_id}"
B->>H: "tab.create (placement)"
B->>H: "agent.start {argv, env, tab_id, cwd}"
H-->>B: "worker in the primary's directory"
```
*Figure 2 — a no-cwd spawn inherits the primary's directory from the caller's PID.*
> **Status:** the inheritance (sources 1–3) is the **target design**; today `bridged` creates one
> shared worker space and workers land in herdr's default cwd. Wiring `cwd` through
> `workspace.create`/`tab.create` is the follow-up that implements this rule.
> **Status:** implemented (CB-112). `bridged` threads the resolved `cwd` onto **`agent.start {cwd}`**
> (verified: the worker process is rooted there), keeping the single shared worker space. On an MCP
> `bridge_spawn` the primary's cwd is auto-detected from the caller's PID; over REST (no MCP caller)
> it is the explicit `cwd` param else the daemon's cwd. Both placements (`tab` and legacy `pane`)
> carry it, since it rides `agent.start`.
## Assumed launcher: `ccs` (Claude Code)