Skip to content

fix(daemon): re-arm the herdr reporter when a pane attaches a session - #2210

Open
kevinjosethomas wants to merge 1 commit into
mainfrom
fix/herdr-reporter-rearm
Open

fix(daemon): re-arm the herdr reporter when a pane attaches a session#2210
kevinjosethomas wants to merge 1 commit into
mainfrom
fix/herdr-reporter-rearm

Conversation

@kevinjosethomas

@kevinjosethomas kevinjosethomas commented Sep 11, 2026

Copy link
Copy Markdown
Member

Problem

The built-in herdr reporter arms only at extension load, with HERDR_* env in scope (withClientEnv window). A daemon-resident session created headless or in a pane that has since closed keeps its stale identity after being attached from another pane, so it never shows in herdr's agents sidebar. The only workaround is a manual /reload, which the TUI refuses while a response is executing.

Proposal

  • Attach now rebinds the session's pane identity to the attaching client (last pane wins). rebindClientEnv overwrites state.clientEnv and propagates to subagents spawned before the rebind, so pi.exec (live read) and every runtime rebuild/reload keep load-time captures and subprocess env consistent with the new pane. The old adopt-if-absent contract remains for create-reuse.
  • The already-loaded reporter cannot re-run its factory, so the daemon also emits herdr:rebind on the session's shared extension bus (new ResourceLoader.emitExtensionEvent). The reporter now registers its listener set even when unarmed and (re)arms its socket identity from the event, claiming the new pane with the live working/idle state immediately.
  • Watchers (agents view, subagent viewers) and headless clients attach without env and never move the identity; plain detach leaves the last pane (herdr drops reports for panes it does not know).
  • The supervisor no longer fabricates its own env for env-less worker attaches. That fallback adopted the daemon-starting pane onto env-less sessions via watcher attaches and would also have re-bound the reporter to a random pane.

Wire protocol is unchanged (attach already carried optional env), so this is backward-compatible; the env contract comments in daemon-protocol.ts and daemon-mode.ts are updated to the new semantics.

Verification

  • npm run check passes (biome, tsgo, linear-ticket, installer, browser-smoke).
  • npx tsx ../../node_modules/vitest/dist/cli.js --run test/herdr-agent-state.test.ts test/daemon-mode.test.ts test/daemon-client-env.test.ts test/daemon-extension-binding.test.ts test/daemon-supervisor-monitor.test.ts test/agent-connection-daemon.test.ts test/sdk-skills.test.ts
  • New tests: the reporter arms from a herdr:rebind event and reports live state, switches panes on later rebinds (last attach wins), ignores rebinds without a complete identity, and unsubscribes on shutdown; a daemon attach with HERDR_* env rebinds the session env and notifies extensions, an env-less attach changes nothing, and subagents inherit the rebind.
  • The 4 daemon-mode passive-subagent and 4 resource-loader failures that reproduce in this environment also fail on pristine origin/main (pre-existing, unrelated).

Note

Medium Risk
Changes daemon attach semantics for Herdr pane identity and extension bus events; scope is integration/daemon attach paths with new test coverage, not auth or data handling.

Overview
Fixes daemon-resident sessions that never showed in Herdr’s agent sidebar after being opened from a pane, because the built-in reporter only captured pane identity at extension load.

Attach now rebinds pane identity (rebindClientEnv): env-carrying attaches overwrite clientEnv with the attaching client’s allowlisted HERDR_* vars (last pane wins), propagate to subagents, and emit herdr:rebind on the session extension bus via new ResourceLoader.emitExtensionEvent. Env-less attaches (watchers, headless) leave identity unchanged. Session create still uses adopt-if-absent when reusing env-less sessions.

Built-in herdr reporter stays loaded even without Herdr env at load: it tracks working/idle/blocked while unarmed, skips socket traffic until armed, then re-arms on herdr:rebind and publishes live state to the new pane. Shutdown unsubscribes the rebind listener.

Supervisor no longer injects its own env on env-less worker attaches (which could wrongly bind sessions to the daemon’s pane).

Wire protocol unchanged; tests cover rebind, env-less attach, subagent propagation, and reporter behavior.

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

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Prime Agent performance — completed

PR edeb9932 compared with main 1eee2938.

Overall: 0 regressed · 0 improved · 17 no clear change.

Metric Main This PR Change
Cold startup 2,828.7 ms 2,912.1 ms ≈ +83.4 ms (+2.95%)
Warm startup 1,700.6 ms 1,691.4 ms ≈ -9.1 ms (-0.54%)
Installation 26.75 s 28.92 s ≈ +2.18 s (+8.13%)
Compressed release artifacts 11.12 MB 11.13 MB ≈ +0.008 MB (+0.07%)
Installed footprint 597.49 MB 597.51 MB ≈ +0.02 MB (+0.00%)
Idle memory, summed RSS 1,017.59 MB 1,025.34 MB ≈ +7.75 MB (+0.76%)

Python runtime

Metric Main This PR Change
Python kernel startup 124.9 ms 125.0 ms ≈ +0.095 ms (+0.08%)
Python cell round trip 0.475 ms 0.578 ms ≈ +0.103 ms (+21.67%)
Empty bash command 11.0 ms 11.8 ms ≈ +0.8 ms (+7.13%)
Bash git status 16.3 ms 17.7 ms ≈ +1.4 ms (+8.47%)
Bash 32 KiB output 11.0 ms 12.1 ms ≈ +1.1 ms (+10.16%)
35 cells / 9 shell calls 157.9 ms 173.6 ms ≈ +15.7 ms (+9.93%)
Python interrupt to done 1.399 ms 1.586 ms ≈ +0.187 ms (+13.35%)
Python state snapshot 24.8 ms 25.5 ms ≈ +0.7 ms (+2.87%)
Python state restore 343.4 ms 359.4 ms ≈ +16.0 ms (+4.66%)
Python idle RSS 35.22 MB 35.30 MB ≈ +0.08 MB (+0.22%)
Python RSS after pandas workload 97.90 MB 97.38 MB ≈ -0.52 MB (-0.54%)

Sandbox cost: ~$0.0922 — no inference calls.
Run, logs, and downloadable raw results

Methodology and samples

Main resolved at 2026-09-11T02:18:36.738706+00:00. Harness 1eee2938.
Linux x64, 4 vCPU, 8 GB RAM, 20 GB disk; region us.
Image: node:24-bookworm@sha256:be23f54a88d34e8824c741b19b91064094f92c1c97b194144bfc8b50d67258e2.
Stock tools, skills, daemon, and Python bootstrap enabled; fresh homes and a fixed Git fixture.
Onboarding is dismissed; the editor starts without a selected model or submitted prompt.
Medians shown. Arrows require a 20% timing/memory change plus absolute floors and IQR.
These practical noise floors are not a statistical significance test.
Cold means stopped Prime processes; OS filesystem caches are not flushed.
No model requests or credentials. Installation excludes build/setup time.
Installer tarballs use loopback; npm/Python downloads use the network with fresh caches.
Artifact size counts release tarballs; footprint after first use includes registry packages.
MB is decimal. Summed RSS can double-count shared pages; PSS is recorded when available.
Provisioning, setup, and build durations are recorded separately in the raw results.
Kernel probes use the installed JSONL runtime, outside the TUI/TypeScript host.
Per trial: 50 Python cells, 5 calls per shell case, and one 35-cell mix (9 git status calls).
Cell/shell values are batch means; other runtime timings are single operations.
State fixture: a 10,000-row × 8-column integer DataFrame and a 10,000-integer list.
Restore runs in a fresh kernel, including pandas imports; kernel startup is excluded.
Kernel RSS covers the isolated Python process; loaded RSS follows the pandas workload.
Costs estimate full sandbox lifetimes at configured rates, including setup and build.
Budget target: $1; not a billing cap. Checks are informational.

Metric Main successful/attempted PR successful/attempted Main spread PR spread
Cold startup 10/10 10/10 IQR 70.7 ms IQR 49.4 ms
Warm startup 10/10 10/10 IQR 100.8 ms IQR 46.1 ms
Installation 3/3 3/3 range 0.95 s range 12.68 s
Compressed release artifacts 1/1 1/1
Installed footprint 1/1 1/1
Idle memory, summed RSS 10/10 10/10 IQR 16.73 MB IQR 14.74 MB
Python kernel startup 10/10 10/10 IQR 5.6 ms IQR 4.5 ms
Python cell round trip 10/10 10/10 IQR 0.024 ms IQR 0.107 ms
Empty bash command 10/10 10/10 IQR 0.7 ms IQR 1.0 ms
Bash git status 10/10 10/10 IQR 1.0 ms IQR 1.5 ms
Bash 32 KiB output 10/10 10/10 IQR 0.6 ms IQR 0.8 ms
35 cells / 9 shell calls 10/10 10/10 IQR 17.6 ms IQR 19.3 ms
Python interrupt to done 10/10 10/10 IQR 0.076 ms IQR 0.184 ms
Python state snapshot 10/10 10/10 IQR 3.1 ms IQR 1.5 ms
Python state restore 10/10 10/10 IQR 39.1 ms IQR 22.8 ms
Python idle RSS 10/10 10/10 IQR 2.87 MB IQR 2.71 MB
Python RSS after pandas workload 10/10 10/10 IQR 2.87 MB IQR 2.77 MB

@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 2 potential issues.

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 edeb993. Configure here.

const clientEnv = filterClientEnv(command.env);
const deferClientEnv = this.updateRestart && this.updateRestart.phase !== "preparing";
if (!deferClientEnv) this.adoptClientEnv(state, clientEnv);
if (!deferClientEnv) this.rebindClientEnv(state, clientEnv);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Failed attach still rebinds pane

Medium Severity

rebindClientEnv runs before createAttachResult succeeds and before the client joins state.clients. A thrown attach still overwrites clientEnv and emits herdr:rebind, so the session reports to a pane that never became a live viewer and disappears from the previous pane's herdr sidebar.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit edeb993. Configure here.

if (state.runtime.metadata.kind === "subagent") {
return;
}
state.runtime.services.resourceLoader.emitExtensionEvent(HERDR_REBIND_EVENT, { env });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Partial env clobbers pane identity

Medium Severity

rebindClientEnv replaces state.clientEnv and emits herdr:rebind for any non-empty filtered attach env. The reporter only arms or moves when HERDR_ENV is 1 with both socket and pane set. A partial or stray allowlisted key therefore wipes exec and reload env while the live reporter stays on the old pane, and a later /reload can disarm reporting entirely.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit edeb993. Configure here.

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