Skip to content

AsyncLocalStorage context lost in tool execution (regression in beta.40) #839

@technopahadi

Description

@technopahadi

Summary

AsyncLocalStorage.getStore() returns undefined inside AI SDK tool execute() callbacks, even though the context is correctly set earlier in the same step.

Regression

Version Result
beta.39 ✅ Works
beta.40 ❌ Broken

Reproduction

// lib/context.ts
import { AsyncLocalStorage } from 'async_hooks';

export const requestContext = new AsyncLocalStorage<{ userId: string }>();

// lib/tool.ts
import { tool } from 'ai';
import { z } from 'zod';
import { requestContext } from './context';

export const myTool = tool({
  description: 'Test tool',
  parameters: z.object({ message: z.string() }),
  execute: async ({ message }) => {
    const store = requestContext.getStore();
    console.log('[Tool] store:', store); // ❌ undefined in beta.40+
    return { success: !!store, store, message };
  },
});

// workflows/test.ts
export async function testWorkflow() {
  'use workflow';
  return await testStep();
}

async function testStep() {
  'use step';

  const { streamText } = await import('ai');
  const { anthropic } = await import('@ai-sdk/anthropic');
  const { requestContext } = await import('@/lib/context');
  const { myTool } = await import('@/lib/tool');

  return requestContext.run({ userId: 'xyz' }, async () => {
    console.log('[Step] Before streamText:', requestContext.getStore()); // ✅ { userId: 'xyz' }

    const result = streamText({
      model: anthropic('claude-sonnet-4-20250514'),
      messages: [{ role: 'user', content: 'Call myTool with message "test"' }],
      tools: { myTool },
      toolChoice: 'required',
    });

    let toolResult = null;
    for await (const chunk of result.fullStream) {
      if (chunk.type === 'tool-result') toolResult = chunk.result;
    }

    return { storeBeforeStreamText: requestContext.getStore(), toolResult };
  });
}

// app/api/test/route.ts
import { NextResponse } from 'next/server';
import { start } from 'workflow/api';
import { testWorkflow } from '@/workflows/test';

export async function GET() {
  const run = await start(testWorkflow, []);
  return NextResponse.json(await run.returnValue);
}

beta.39 result:

{"storeBeforeStreamText":{"userId":"xyz"},"toolResult":{"success":true,"store":{"userId":"xyz"},"message":"test"}}

beta.40 result:

{"storeBeforeStreamText":{"userId":"xyz"},"toolResult":{"success":false,"message":"test"}}

Root Cause

The bundler creates duplicate module instances. The step and tool end up using different AsyncLocalStorage instances.

Environment

  • workflow: 4.0.1-beta.40+
  • Node.js: 24
  • Next.js: 16.0.10
  • ai: 6.0.45

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions