Skip to content

useStream hook: stale client closure causes 403 errors after token refresh #1840

@EUN-GYUL

Description

@EUN-GYUL

Bug Description

The useStream hook in @langchain/langgraph-sdk/react uses a stale client reference after the apiKey prop changes. This causes HTTP 403 authentication errors when the JWT token is refreshed while the component is mounted.

Root Cause

In useStream.ts, the useThreadHistory callback captures the initial client object in a closure:

// From node_modules/@langchain/langgraph-sdk/dist/react/useStream.js
const useThreadHistory = useCallback(
  async (threadId, options) => {
    // Uses `client` from closure - never updates when apiKey changes
    const history = await client.threads.getHistory(threadId, options);
    // ...
  },
  [] // Empty dependency array - closure is never refreshed
);

The client is created from apiKey:

const client = useMemo(() => {
  if (!apiUrl) return null;
  return new Client({ apiUrl, apiKey });
}, [apiUrl, apiKey]);

While client updates when apiKey changes, useThreadHistory still holds the old client reference due to the empty dependency array.

Reproduction Steps

  1. Mount a component using useStream with an initial JWT token
  2. Token expires/refreshes, parent component passes new apiKey prop
  3. User sends a message via submit()
  4. Expected: Request uses new token
  5. Actual: Request uses old token → 403 error

Minimal Reproduction

function ChatComponent({ token }) {
  const stream = useStream({
    apiUrl: "https://api.example.com",
    apiKey: token, // Changes when refreshed
    assistantId: "my-assistant",
    threadId: null,
  });

  // After token refresh, submit() still uses old token
  return <button onClick={() => stream.submit(...)}>Send</button>;
}

Current Workaround

Force component remount when token changes using React key:

<ChatComponent key={token || "no-token"} token={token} />

This destroys and recreates the entire component, creating a new client instance.

Proposed Fix

Add client to the dependency array of useThreadHistory and other callbacks:

const useThreadHistory = useCallback(
  async (threadId, options) => {
    const history = await client.threads.getHistory(threadId, options);
    // ...
  },
  [client] // Add client dependency
);

Environment

  • @langchain/langgraph-sdk: ^0.0.41
  • React: 19.x
  • Next.js: 15.x

Related Issues

Impact

  • Prevents seamless token refresh in production apps
  • Forces unnecessary component remounts as workaround
  • Affects any app using JWT authentication with token rotation

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