Skip to content

fix(pi): capture isIdle before awaiting reportSession to avoid stale ctx throws - #2194

Closed
ccharname wants to merge 1 commit into
herdrdev:masterfrom
ccharname:fix/pi-extension-stale-ctx
Closed

fix(pi): capture isIdle before awaiting reportSession to avoid stale ctx throws#2194
ccharname wants to merge 1 commit into
herdrdev:masterfrom
ccharname:fix/pi-extension-stale-ctx

Conversation

@ccharname

Copy link
Copy Markdown

Problem

The pi integration's session_start handler awaited reportSession() (up to ~2s of socket retries) before reading ctx.isIdle():

pi.on("session_start", async (event, ctx) => {
  ...
  await reportSession(event?.reason);
  agentActive = ctx?.isIdle?.() === false; // stale ctx if session replaced meanwhile
  publishState(true);
});

If the session is replaced/reloaded during that window (ctx.newSession() / ctx.fork() / ctx.switchSession() / ctx.reload()), pi invalidates the captured extension ctx and the post-await access throws This extension ctx is stale after session replacement or reload. Because the throw happens inside an async event handler, it surfaces as an unhandled promise rejection — which by default terminates the process on modern Node, killing the agent pane.

Fix

Read isIdle synchronously before the await; the value cannot change across the report since a replacement emits a fresh session_start on the new runtime anyway:

const wasActive = ctx?.isIdle?.() === false;
await reportSession(event?.reason);
agentActive = wasActive;

Verification

bun test src/integration/assets/herdr-agent-state.test.ts — 14/14 pass, including the reload-preserves-state and replacement-report tests.

…ctx throws

session_start awaited reportSession() (up to ~2s of socket retries) before
reading ctx.isIdle(). If the session is replaced/reloaded during that window
(ctx.newSession/fork/switchSession/reload), the captured extension ctx is
invalidated and the post-await access throws 'extension ctx is stale',
which can kill the agent pane process under Node's unhandled rejection
default. Read isIdle synchronously before the await.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 01ee9494-9196-4e58-bb89-cc0222ab0477

📥 Commits

Reviewing files that changed from the base of the PR and between 9a4ce5e and cc8fc70.

📒 Files selected for processing (1)
  • src/integration/assets/pi/herdr-agent-state.ts

📝 Walkthrough

Walkthrough

The session_start handler now captures the agent state before asynchronous reporting and reuses that state afterward. This avoids reading a potentially replaced session context after reportSession completes.

Changes

Session state capture

Layer / File(s) Summary
Capture state before session reporting
src/integration/assets/pi/herdr-agent-state.ts
session_start evaluates ctx.isIdle() before reportSession and assigns the captured active-state result after reporting.

Estimated code review effort: 2 (Simple) | ~5 minutes

Possibly related PRs

  • herdrdev/herdr#2159: Modifies the same session_start session-state handling in herdr-agent-state.ts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the fix for stale context errors by capturing isIdle before awaiting reportSession.
Description check ✅ Passed The description explains the stale context problem, the synchronous state capture fix, and the test verification.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@kangal-bot kangal-bot added the ai-review Trigger automated AI reviews for pull requests admitted by the PR gate label Aug 2, 2026
@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown

Greptile Summary

The PR moves the pi integration's isIdle read before the asynchronous session report to avoid accessing an invalidated context.

  • Captures the activity state synchronously during session_start.
  • Applies and publishes the captured state after session reporting completes.
  • Leaves an out-of-order completion path where a superseded handler can overwrite replacement-session state.

Confidence Score: 4/5

The PR should not merge until a superseded session_start handler is prevented from overwriting and publishing the replacement session's activity state.

Independent asynchronous session reports can complete out of order, and the older handler unconditionally commits its pre-await snapshot to shared state after it resumes.

Files Needing Attention: src/integration/assets/pi/herdr-agent-state.ts

Important Files Changed

Filename Overview
src/integration/assets/pi/herdr-agent-state.ts The synchronous context read avoids the stale-context exception, but the delayed unconditional assignment can publish superseded session state when reports complete out of order.

Sequence Diagram

sequenceDiagram
    participant O as Old session_start
    participant N as New session_start
    participant S as Shared agent state
    O->>O: Capture old isIdle
    O->>O: Await reportSession
    N->>N: Capture new isIdle
    N->>N: Await reportSession
    N->>S: Set and publish new state
    O->>S: Resume, overwrite and publish old state
Loading

Reviews (1): Last reviewed commit: "fix(pi): capture isIdle before awaiting ..." | Re-trigger Greptile

Comment on lines 235 to +236
// A reload can replace this extension mid-run without emitting another agent_start.
agentActive = ctx?.isIdle?.() === false;
agentActive = wasActive;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Superseded handler overwrites current state

If replacement and original session_start handlers overlap, the original handler can resume after the replacement, assign its earlier wasActive snapshot to the shared agentActive, and force-publish an incorrect idle or working state.

@ogulcancelik

Copy link
Copy Markdown
Collaborator

i can't repro a case where session gets replaced between session_start and reportSession(). could you open an issue with exact repro case please?

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

Labels

ai-review Trigger automated AI reviews for pull requests admitted by the PR gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants