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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions