Skip to content

Pass herdr env vars from interactive client through daemon protocol to extensions - #276

Closed
samsja wants to merge 4 commits into
mainfrom
feat/herdr-env-passthrough
Closed

Pass herdr env vars from interactive client through daemon protocol to extensions#276
samsja wants to merge 4 commits into
mainfrom
feat/herdr-env-passthrough

Conversation

@samsja

@samsja samsja commented Jun 27, 2026

Copy link
Copy Markdown
Member

Problem

The daemon architecture means extensions run in the daemon process, not the interactive TUI client. The daemon inherits env vars from the first process that spawns it — so when a new herdr pane connects to an existing daemon, the herdr-agent-state extension reports agent state to the wrong pane (the one that originally started the daemon).

Fix

Add DaemonClientEnv to the daemon protocol so the interactive client can forward environment variables (e.g. HERDR_PANE_ID, HERDR_SOCKET_PATH) to the daemon. The daemon applies these to process.env before extensions are loaded or rebound, ensuring extensions always see the correct client-side env vars.

Changes

  • daemon-protocol.ts: Add DaemonClientEnv interface with optional env?: Record<string, string>. Extend create and attach commands to carry it.
  • active-session-state.ts: Add clientEnv? field to DaemonSocketClient.
  • daemon-mode.ts: Add applyClientEnvToProcess() helper (curated allowlist of herdr env keys). Called in create handler (before createRuntime → extension binding) and attach handler.
  • daemon-agent-connection.ts: Add collectClientEnv() helper. attach() now sends env with the command.
  • main.ts: Add collectClientEnv() helper. create command now includes env.

How it works

herdr pane w2:p1 starts → client has HERDR_PANE_ID=w2:p1
  → sends "create" with env: {HERDR_PANE_ID: "w2:p1", ...}
  → daemon applies to process.env BEFORE loading extensions
  → extension reads process.env.HERDR_PANE_ID → "w2:p1" ✓

herdr pane w3:p1 starts → client has HERDR_PANE_ID=w3:p1
  → sends "attach" with env: {HERDR_PANE_ID: "w3:p1", ...}
  → daemon updates process.env.HERDR_PANE_ID to "w3:p1"
  → extension reads process.env dynamically → "w3:p1" ✓

Notes

  • Only a curated set of env keys (HERDR_ENV, HERDR_PANE_ID, HERDR_SOCKET_PATH, HERDR_TAB_ID, HERDR_WORKSPACE_ID) is applied — no blanket env var passthrough.
  • A single shared daemon now correctly serves multiple herdr panes without needing per-pane daemon sockets.

Note

Low Risk
No code is modified in the provided diff, so there is no functional or security impact.

Overview
This PR contains only an empty snapshot with no functional changes.

The description refers to forwarding herdr env vars through the daemon protocol, but those pieces are not present in this diff.

Reviewed by Cursor Bugbot for commit 147641b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Pass herdr environment variables from interactive client through daemon protocol to extensions

This PR contains only blank line modifications with no functional changes.

Macroscope summarized 147641b.

…o extensions

The daemon architecture means extensions run in the daemon process, not
the interactive TUI client. The daemon inherits env vars from the first
process that spawns it, so when a new herdr pane connects to an existing
daemon, the extension reports state to the wrong pane.

Add DaemonClientEnv to the create and attach daemon commands so the
interactive client can forward herdr env vars (HERDR_PANE_ID,
HERDR_SOCKET_PATH, etc.) to the daemon. The daemon applies these to
process.env before extensions are loaded or rebound, ensuring the
herdr-agent-state extension always reports to the correct pane.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a8217da. Configure here.

Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts Outdated
"HERDR_WORKSPACE_ID",
] as const;

/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High daemon/daemon-mode.ts:86

applyClientEnvToProcess writes the client's HERDR_* values into the shared process.env, but it is only called during create and attach. When two clients are attached to the same daemon, the last client to attach overwrites the previous client's values in process.env, and subsequent commands from the other client (e.g. prompt) never re-apply that client's clientEnv before running extensions. This means extensions see whichever pane attached last, not the pane that issued the current command. Consider re-applying the invoking client's clientEnv at the start of each command that may run extensions, or isolate the values per-call rather than mutating shared process.env.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/daemon/daemon-mode.ts around line 86:

`applyClientEnvToProcess` writes the client's `HERDR_*` values into the shared `process.env`, but it is only called during `create` and `attach`. When two clients are attached to the same daemon, the last client to attach overwrites the previous client's values in `process.env`, and subsequent commands from the other client (e.g. `prompt`) never re-apply that client's `clientEnv` before running extensions. This means extensions see whichever pane attached last, not the pane that issued the current command. Consider re-applying the invoking client's `clientEnv` at the start of each command that may run extensions, or isolate the values per-call rather than mutating shared `process.env`.

Comment on lines +91 to +98
function applyClientEnvToProcess(env?: Record<string, string>): void {
if (!env) return;
for (const key of CLIENT_ENV_KEYS) {
if (env[key] !== undefined) {
process.env[key] = env[key];
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium daemon/daemon-mode.ts:91

applyClientEnvToProcess() only sets keys present in env and never clears keys that are absent. When one client with HERDR_* variables creates or attaches first, those values persist in process.env; a later client connecting without some or all of those variables inherits the stale values, so extensions target the previous client's pane/socket instead of the current one. Consider deleting each CLIENT_ENV_KEYS entry from process.env before applying the incoming env so missing keys are cleared.

 function applyClientEnvToProcess(env?: Record<string, string>): void {
+	if (env) {
+		for (const key of CLIENT_ENV_KEYS) {
+			delete process.env[key];
+		}
+	}
 	if (!env) return;
 	for (const key of CLIENT_ENV_KEYS) {
 		if (env[key] !== undefined) {
 			process.env[key] = env[key];
 		}
 	}
 }
Also found in 1 other location(s)

packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts:77

collectClientEnv() only includes keys that are currently defined, so a later non-herdr client sends no entry for a previously-set HERDR_* variable. On the daemon side applyClientEnvToProcess() only overwrites provided keys and never deletes missing ones, so stale pane/workspace values remain in process.env and extensions keep reporting to the old pane after attaching from a client without those vars.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/daemon/daemon-mode.ts around lines 91-98:

`applyClientEnvToProcess()` only sets keys present in `env` and never clears keys that are absent. When one client with `HERDR_*` variables creates or attaches first, those values persist in `process.env`; a later client connecting without some or all of those variables inherits the stale values, so extensions target the previous client's pane/socket instead of the current one. Consider deleting each `CLIENT_ENV_KEYS` entry from `process.env` before applying the incoming `env` so missing keys are cleared.

Also found in 1 other location(s):
- packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts:77 -- `collectClientEnv()` only includes keys that are currently defined, so a later non-herdr client sends no entry for a previously-set `HERDR_*` variable. On the daemon side `applyClientEnvToProcess()` only overwrites provided keys and never deletes missing ones, so stale pane/workspace values remain in `process.env` and extensions keep reporting to the old pane after attaching from a client without those vars.

# Conflicts:
#	packages/coding-agent/src/main.ts
#	packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts
#	packages/coding-agent/src/modes/daemon/active-session-state.ts
#	packages/coding-agent/src/modes/daemon/daemon-mode.ts
#	packages/coding-agent/src/modes/daemon/daemon-protocol.ts
zhengr pushed a commit to zhengr/prime-agent that referenced this pull request Aug 8, 2026
- SettingsManager now loads .pi/settings.json from cwd (project settings)
- Project settings merge with global settings (deep merge for objects)
- Setters only modify global settings, project settings are read-only
- Add static factories: SettingsManager.create(cwd?, agentDir?), SettingsManager.inMemory(settings?)
- Add applyOverrides() for programmatic overrides
- Replace 'settings' option with 'settingsManager' in CreateAgentSessionOptions
- Update examples to use new pattern

Incorporates PR PrimeIntellect-ai#276 approach
@SandroHub013

Copy link
Copy Markdown

The protocol env forwarding addresses pane rebinding, but Windows also needs endpoint normalization at the socket client. Herdr exposes HERDR_SOCKET_PATH as a path-like marker such as C:\Users\...\herdr.sock; passing it directly to net.createConnection() returns ENOTSOCK. The working Windows endpoint is \\.\pipe\ plus that value.

I reproduced this against a live Herdr server and added a local adapter that preserves Unix and already-normalized pipe paths, with Windows endpoint/session-path tests. A Windows test for this boundary would keep env forwarding from appearing fixed while reporting still silently no-ops.

@samsja samsja closed this Aug 22, 2026
thomaswillner pushed a commit to thomaswillner/prime-agent that referenced this pull request Aug 29, 2026
Records, without editing the now-false text away, that sections 9 and 4 went
stale five hours after they were written. The correction matters more than the
content: this file exists to stop sessions trusting notes over GitHub, and it
caught its own author.

- main is 8d2139c. Between 16:12Z and 21:54Z the fleet merged PrimeIntellect-ai#279 (the PrimeIntellect-ai#58
  alert-bridge race, FIXED — stop carrying it as a standing exception), PrimeIntellect-ai#278
  (AGENTS.md invariants), PrimeIntellect-ai#283 (repo cleanup), and PrimeIntellect-ai#284, which delivered the
  last brief and closed PrimeIntellect-ai#165 with a keyword.
- Section 3's routing conclusion was confirmed by events: PrimeIntellect-ai#271 was delivered
  by the Mac maker fleet via auto-dispatch, exactly as argued, and the remote
  session correctly declined to open a second lane.
- Flags issue-state drift: PrimeIntellect-ai#271, PrimeIntellect-ai#276 and PrimeIntellect-ai#274 are delivered and merged yet
  still open, because a title reference is not a closing keyword. That is the
  mirror image of the hazard the V2 CLAUDE.md documents, and it leaves
  open-work disagreeing with main. Operator action, named as such.
- Records the residual PrimeIntellect-ai#284 deferred on stated grounds (PrimeIntellect-ai#286), which is a
  known open edge on the LIVE path.
- States the next slice: S4 / PrimeIntellect-ai#236, the first whose exit criteria need a real
  broker order. Certification stays 0/12; the system has never placed a trade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5B7QM1QLQuWSBMCxiCzS6
thomaswillner added a commit to thomaswillner/prime-agent that referenced this pull request Aug 29, 2026
…e-notes lesson (#13)

* docs(spx-v2): verification pass, queue state, and self-refinement record

No code written this session — a verification pass over already-delivered
work plus the queue-state answer.

Records, so future sessions do not repeat them:

- The audit-challenge / V1-coverage / rag-tot-cot-challenge / corrected-input
  deliverable ALREADY EXISTS (AUDIT_CHALLENGE sections A-D and
  PRIME_AGENT_INPUT_SPX_V2). An operator prompt has now asked for it in at
  least two sessions; redoing it is inventing work.
- Verified queue state from GitHub: PrimeIntellect-ai#266/PR PrimeIntellect-ai#268 merged (and PrimeIntellect-ai#263 with it,
  now main f64029a); PrimeIntellect-ai#265/PR PrimeIntellect-ai#269 and PrimeIntellect-ai#264/PR PrimeIntellect-ai#270 open with CI in flight;
  PrimeIntellect-ai#272 and PrimeIntellect-ai#271 filed, unstarted, no lane.
- Errors and corrections: settle elapsed time from GitHub workflow-run
  timestamps, never the container clock; add_repo push access was
  classifier-denied so a remote session may hold read-only and cannot push;
  register_repo_root denial falls back to reading CLAUDE.md directly.
- MATS/superpowers/routing settled empirically with the exact commands used,
  so the search is not repeated: they are Mac-harness resident, and PrimeIntellect-ai#272/PrimeIntellect-ai#271
  already carry auto-dispatch, which is what routes them to the maker fleet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5B7QM1QLQuWSBMCxiCzS6

* docs(spx-v2): implementation-session addendum — access, setup, CI outage, self-review

Appends the second half of the session to the notes: implementing PrimeIntellect-ai#272 after
the operator corrected two access assumptions.

The corrections matter more than the code:
- push DOES work; "I cannot push" was inferred from add_repo's access label
  rather than tested. A dry-run push proved it. Also: the refspec push form is
  classifier-denied while `git push -u origin <branch>` succeeds.
- this host is not the MacBook (uname, no /Users, no ~/.prime).

Also records the environment setup the Makefile assumes (venv before
v2-install, ruff 0.15.22 via python -m, seeding the gitignored account.yaml,
and proving PYTHONPATH beats editable installs in a worktree), the method that
diagnosed the repo-wide CI outage in two calls (zero recorded steps, then the
same workflow red on main), and two test defects self-review caught before
pushing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5B7QM1QLQuWSBMCxiCzS6

* docs(spx-v2): final queue state — PrimeIntellect-ai#273 merged, only PrimeIntellect-ai#271 remains

Closes the record for this session.

- All five briefed PRs merged (PrimeIntellect-ai#268, PrimeIntellect-ai#263, PrimeIntellect-ai#270, PrimeIntellect-ai#269, PrimeIntellect-ai#273); main is c84855d.
  Issues PrimeIntellect-ai#266 and PrimeIntellect-ai#272 closed by their PRs. PrimeIntellect-ai#271 is the only open brief and
  was never authorised, so never started. Runtime testing is unblocked.
- The CI outage (13:51Z-15:47Z) was account-level and hit main identically;
  recovery was visible as `changes` taking 9s with real steps instead of 2s
  with none. Nothing in the diff ever needed changing.
- Records the scope misjudgement worth carrying forward: a Codex P1 mapped
  directly to an acceptance checkbox I had deferred as out of scope. When a
  finding maps to an acceptance criterion it is in scope by definition.
- Records the auto-merge hazard: squash composes the commit message from the
  PR body, so a body left stale after a review round writes false claims into
  main permanently. Rewrite the body before merge; keep corrections visible.
- Records a published test claim that had not been executed, and the rule
  that follows from it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5B7QM1QLQuWSBMCxiCzS6

* docs(spx-v2): correct the queue state — the last brief landed while idle

Records, without editing the now-false text away, that sections 9 and 4 went
stale five hours after they were written. The correction matters more than the
content: this file exists to stop sessions trusting notes over GitHub, and it
caught its own author.

- main is 8d2139c. Between 16:12Z and 21:54Z the fleet merged PrimeIntellect-ai#279 (the PrimeIntellect-ai#58
  alert-bridge race, FIXED — stop carrying it as a standing exception), PrimeIntellect-ai#278
  (AGENTS.md invariants), PrimeIntellect-ai#283 (repo cleanup), and PrimeIntellect-ai#284, which delivered the
  last brief and closed PrimeIntellect-ai#165 with a keyword.
- Section 3's routing conclusion was confirmed by events: PrimeIntellect-ai#271 was delivered
  by the Mac maker fleet via auto-dispatch, exactly as argued, and the remote
  session correctly declined to open a second lane.
- Flags issue-state drift: PrimeIntellect-ai#271, PrimeIntellect-ai#276 and PrimeIntellect-ai#274 are delivered and merged yet
  still open, because a title reference is not a closing keyword. That is the
  mirror image of the hazard the V2 CLAUDE.md documents, and it leaves
  open-work disagreeing with main. Operator action, named as such.
- Records the residual PrimeIntellect-ai#284 deferred on stated grounds (PrimeIntellect-ai#286), which is a
  known open edge on the LIVE path.
- States the next slice: S4 / PrimeIntellect-ai#236, the first whose exit criteria need a real
  broker order. Certification stays 0/12; the system has never placed a trade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5B7QM1QLQuWSBMCxiCzS6

---------

Co-authored-by: Claude <noreply@anthropic.com>
@kevinjosethomas
kevinjosethomas deleted the feat/herdr-env-passthrough branch September 8, 2026 20:41
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.

4 participants