Skip to content

fix(session): preserve tool metadata across pending→running transition#20198

Closed
rmk40 wants to merge 1 commit intoanomalyco:devfrom
rmk40:test/subagent-click-metadata-race
Closed

fix(session): preserve tool metadata across pending→running transition#20198
rmk40 wants to merge 1 commit intoanomalyco:devfrom
rmk40:test/subagent-click-metadata-race

Conversation

@rmk40
Copy link
Copy Markdown
Contributor

@rmk40 rmk40 commented Mar 31, 2026

Issue for this PR

Closes #20184

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

The AI SDK fires tool execute() as a detached promise before the processor handles the tool-call stream event. When execute() calls ctx.metadata({sessionId}), the processor's tool-call handler overwrites the DB with a fresh {status: "running", input, time} state that drops the metadata. This makes subagent sessions unclickable in the TUI because the TUI reads state.metadata.sessionId for navigation.

The fix adds a synchronous toolMetadata map on the processor context. setToolMetadata() on the Handle writes to this map immediately when the metadata callback fires. The tool-call handler then reads from the map and merges title/metadata into the running state. prompt.ts's metadata callback calls setToolMetadata() synchronously before its existing async DB update, so metadata is captured regardless of event ordering.

This PR also introduces two reusable test fixtures:

  • test/fixture/anthropic.ts — Fake Anthropic HTTP server (Bun.serve({port:0})) with SSE response helpers (toolResponse, textResponse, waitRequest, deferred). Reusable for any test that needs to exercise the real AI SDK streamText pipeline against a fake provider.
  • test/fixture/prompt-layers.ts — Full SessionPrompt Effect layer stack with real LLM.defaultLayer and no-op stubs for MCP/LSP/FileTime. Reusable for any test that needs the complete prompt pipeline (tool registration, resolveTools, processor event handling) without mocking the LLM.

Related: #20183

How did you verify your code works?

  • E2E regression test (test/session/metadata-race.test.ts) that goes through the full production path: ToolRegistry.registerSessionPrompt.loopresolveToolsctx.metadata()setToolMetadata → processor tool-call handler. Uses the fake Anthropic server fixture with real AI SDK streamText. Asserts metadata.sessionId is present on the running tool part in the DB.
  • Test passes 3/3 consistently (~1s each), no flakiness.
  • Test does not compile without the fix (setToolMetadata missing from Handle), confirming it catches the regression.
  • Existing prompt-effect.test.ts (23 tests) and processor-effect.test.ts (10 tests) still pass.
  • Manual testing confirmed subagent sessions are clickable again.

Screenshots / recordings

N/A — data layer fix, not UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions
Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Based on my search results, I found one related PR:

Related PR Found:

Why it's related: PR #20183 addresses the same issue (preserving metadata during session state transitions) and is explicitly mentioned in the current PR description as a related fix. Both are addressing the problem of metadata being lost when a session transitions from pending to running state, which prevents subagent session navigation from working in the TUI.

The current PR (#20198) appears to be a more comprehensive fix using a side-channel approach, while #20183 may be an earlier attempt or partial fix for the same root cause.

@rmk40 rmk40 force-pushed the test/subagent-click-metadata-race branch from 6498eb6 to 367f492 Compare March 31, 2026 06:45
@github-actions github-actions bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Mar 31, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

The AI SDK fires tool execute() as a detached promise before the
processor handles the tool-call stream event. When execute() calls
ctx.metadata({sessionId}), the processor's tool-call handler
overwrites the DB with a fresh running state that has no metadata,
making subagent sessions unclickable in the TUI.

- Add synchronous toolMetadata side-channel on processor context
- setToolMetadata() on Handle writes to the map immediately
- tool-call handler reads and merges metadata from the side-channel
- prompt.ts metadata callback calls setToolMetadata() before the
  async DB update, ensuring metadata is captured regardless of
  event ordering
- Add E2E regression test using fake Anthropic HTTP server through
  the real AI SDK streamText pipeline with gate-based assertion of
  running-state metadata

Closes anomalyco#20184
@rmk40 rmk40 force-pushed the test/subagent-click-metadata-race branch from 367f492 to 2747b5e Compare March 31, 2026 06:56
@rmk40
Copy link
Copy Markdown
Contributor Author

rmk40 commented Mar 31, 2026

Split into two PRs: #20208 (test fixtures) and a follow-up PR (fix + regression test).

@rmk40 rmk40 closed this Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Clicking on subagent labels in TUI does not navigate to subagent session

1 participant