Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

chat(): sign the state of a suspended run so a resume without persistence cannot change it

Abierto
#1,740 2 comentarios 0 reacciones 1 asignado Ver en GitHub

Los mantenedores suelen responder en 1 día

@AlemTuzlak ya está trabajando en esto.

Desde el 13/10/2026.

Evaluación

Este issue todavía no se ha evaluado.

Descripción

waiting-on: maintainer

Without persistence, chat() trusts the client for the state of a suspended run. Sign that state on the server when the run suspends, and verify it when the run resumes.

Problem

A run that ends with outcome: interrupt waits for the client to send answers. With no persistence middleware, the server keeps nothing between the two requests. The resume reads this state back from the client:

  • The pending tool calls and their arguments. chat() rebuilds them from the message history in the request.
  • The subagent router plan on SUBAGENT_STARTED metadata: agent names, order, and (in #1722) each agent's input.

Nothing in this state is signed. The interrupt binding under tanstack:interruptBinding holds SHA-256 hashes of the tool schemas (defaultInterruptHash in packages/ai/src/interrupt-serialization.ts). The hash has no key. It finds a schema that changed between the two requests. It does not find a client that changed the state.

Effects:

  • A client can change the arguments of a pending tool call after the user saw the approval prompt.
  • A client can change which agents of the chat a routed resume runs.
  • A router is server code, so an app can put a trusted value in a routed input, such as a tenant id. A client can replace it. A schema check does not help, because the new value has the same shape.

With persistence, the server checks a resume against its stored record. This issue does not apply there.

Scope

All interrupts that resume through the client, not only subagents:

  • Tool approvals (needsApproval).
  • Client tools.
  • Generic interrupts from onInterruptBoundary.
  • Interrupts raised in a subagent, and the router plan.

All four leave the run through the same suspend point and come back through the same resume point. One signature at that point covers them.

Proposal

  1. Add one server secret to chat(), for example signingSecret.
  2. When a run suspends, build one canonical JSON value that holds:
    • the thread id and the run id
    • each pending interrupt: id, tool name, arguments, schema hashes
    • the router plan and its inputs, if the turn has one
    • an expiry time
  3. Compute an HMAC-SHA256 of that value and send it with the interrupts.
  4. On resume, build the same value from the request, compute the HMAC again, and compare. If the values differ, or the expiry time is past, fail the resume with InterruptResumeValidationError and code stale.

Parts that exist:

  • canonicalInterruptJson gives a stable serialization.
  • Web Crypto has HMAC in each runtime the library supports, so no new dependency is necessary.

Open questions

  • Where the signature travels. An AG-UI resume entry has an interrupt id, a status, and a payload. The signature must be on a field that each client sends back. Use an AG-UI field for this. Do not add a new wire format.
  • No secret set. Option: signing is opt-in at first, and a resume with no signature works as it does now, with a warning in development. A later breaking release then requires a secret or persistence.
  • Generic interrupts. Confirm that their resume path goes through the same point as tool approvals and client tools.
  • Key rotation. A new secret makes each suspended run stale. Decide if chat() accepts more than one secret for verification.

Limits

  • The server still trusts the earlier message history. The signature covers the suspended state only.
  • A client can send the same resume two times. The expiry time limits this. To stop it, the server must keep state.
  • The client can still read the arguments and the inputs. That needs encryption, which is not part of this issue.

Related

  • #1722 adds routed inputs to the plan. Until this issue is done, the safe behavior is to keep routed inputs off the wire, so a turn with routed inputs cannot resume.
  • A second way to get the same result is to keep the plan on the server through routedSubagentPersistence. That needs persistence. Signing does not.
Lenguaje dominante
TypeScript
Estrellas
3.2k
Forks
365
Merge medio
1 d 9 h
PR fusionados (30 d)
242

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de TanStack/ai

Todos los issues de TanStack/ai

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.