Skip to content

fix(voice): propagate job context into tool execute on Node 24#1592

Open
gidiupgidi wants to merge 1 commit into
livekit:mainfrom
gidiupgidi:fix/tool-execute-job-context-node24
Open

fix(voice): propagate job context into tool execute on Node 24#1592
gidiupgidi wants to merge 1 commit into
livekit:mainfrom
gidiupgidi:fix/tool-execute-job-context-node24

Conversation

@gidiupgidi
Copy link
Copy Markdown

@gidiupgidi gidiupgidi commented May 25, 2026

Fixes #1255.

Problem

On Node 24, getJobContext() throws no job context found when called inside a tool's execute() function. The workaround NODE_OPTIONS=--no-async-context-frame reverts Node 24 to the legacy async_hooks-based AsyncLocalStorage, which propagates the job context through implicitly.

Root cause

Tool execution constructs a Task via Task.from(async () => …) that wraps three AsyncLocalStorage contexts inside the task body:

  • agentActivityStorage — wrapped in agent_activity.ts
  • speechHandleStorage — wrapped in agent_activity.ts
  • functionCallStorage — wrapped in generation.ts

jobContextStorage is not wrapped anywhere along this path. With the old async_hooks-based AsyncLocalStorage (Node 22 / --no-async-context-frame), the job context propagated implicitly across the Task.from() boundary. With Node 24's default AsyncContextFrame implementation, it does not, so getJobContext() returns undefined inside tool execute functions. See nodejs/node#58204 for background on the AsyncContextFrame change.

Fix

In performToolExecutions (agents/src/voice/generation.ts):

  1. Capture the current JobContext synchronously at the top of executeToolsTask, where it is still in scope.
  2. Wrap each per-tool Task.from(...) body in runWithJobContextAsync(currentJobContext, ...) so the context is re-established inside the new async scope, alongside the existing functionCallStorage.run(...) wrap.

If no job context is in scope (e.g. unit tests calling performToolExecutions directly), the wrap is skipped so behaviour is unchanged.

Test plan

  • pnpm -F @livekit/agents build succeeds
  • pnpm -w lint:fix clean (no new warnings)
  • pnpm -w format:check and pnpm throws:check clean
  • pnpm test agents — full agents suite (60 files / 840 tests) passes on Node 24.10.0
  • Manually validated against a real voice agent on Node 24 without --no-async-context-frame: getJobContext() inside a tool's execute() returns the room name as expected. (Reporter — please verify with your repro.)

Note on CI coverage

The Build and Test workflows pin actions/setup-node to Node 20, where this bug does not manifest in the first place (Node 20 still uses the async_hooks-based AsyncLocalStorage that propagates job context implicitly). The fix is a safe no-op on Node 20 — if no job context is in scope at the synchronous capture point, the extra runWithJobContextAsync(...) wrap is skipped. So CI going green confirms no regression on Node 20 but cannot confirm the fix is effective on Node 24; that has to come from the reporter's repro.

I tried adding a focused unit-level regression test that wraps performToolExecutions in runWithJobContextAsync and calls getJobContext() from a tool, but the bug does not reproduce in that minimal harness on Node 24 — presumably because the lost-propagation only manifests through the deeper Task.from() chain established by the real agent lifecycle. Happy to add a heavier integration-level test if the maintainers can point me at the right harness.

Re-establish jobContextStorage inside the per-tool Task.from() body in
performToolExecutions so getJobContext() works from a tool's execute()
function on Node 24. Node 24's default AsyncContextFrame AsyncLocalStorage
implementation does not propagate the job context across the Task.from()
boundary the way the legacy async_hooks implementation did, which
previously caused getJobContext() to throw "no job context found" inside
tools (see livekit#1255).
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 25, 2026

🦋 Changeset detected

Latest commit: da12cbf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 33 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 25, 2026

CLA assistant check
All committers have signed the CLA.

@gidiupgidi gidiupgidi marked this pull request as ready for review May 25, 2026 04:54
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

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.

getJobContext() fails inside tool execute functions on Node 24 (AsyncContextFrame context loss)

2 participants