`fork_session` hangs forever on a cyclic `parentUuid` chain (no cycle guard in `_build_fork_lines`)

Abierto Apto para principiantes
#1,506 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 2 días

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
72/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
python
Área
backend

Línea de trabajo

Empieza con el recorrido de parentUuid en _build_fork_lines en src/claude_agent_sdk/_internal/session_mutations.py (alrededor de las líneas 411-418) y compáralo con la protección walk_seen de _build_conversation_chain en src/claude_agent_sdk/_internal/sessions.py. Ejecuta la reproducción con un tiempo de espera acotado y luego añade un conjunto de ids vistos para que el bucle se corte al repetirse un id de padre. Estará terminado cuando el caso cíclico progress devuelva una transcripción bifurcada y la línea base acíclica siga reasignando el parentUuid de la hoja.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

bug good first issue

Summary

_build_fork_lines walks the parentUuid chain with no seen-set or iteration bound. If a session transcript contains a cycle of progress entries (or a single self-parenting progress entry), fork_session / fork_session_via_store loop forever, hanging the calling thread with no timeout or error.

This is a correctness/robustness bug, not a security vulnerability: triggering it requires a transcript file the caller already controls.

Version

  • Repo: anthropics/claude-agent-sdk-python
  • Package: claude-agent-sdk 0.2.181
  • Commit: 22b8eb9c19adfa78759b79e22bd664a57843bb06 (main, 2026-11-12)

Location

src/claude_agent_sdk/_internal/session_mutations.py:411-418 (_build_fork_lines):

while parent_id:
    parent = by_uuid.get(parent_id)
    if not parent:
        break
    if parent.get("type") != "progress":
        new_parent_uuid = uuid_mapping.get(parent_id)
        break
    parent_id = parent.get("parentUuid")

by_uuid includes progress entries (:396-398), and neither _parse_fork_transcript (:601-613) nor the fork_session_via_store partition (:937-946) validates parentUuid shape — they check only type and uuid — so a cyclic graph reaches the walk unchecked.

Note the sibling walk in _build_conversation_chain guards the same traversal with a walk_seen set (src/claude_agent_sdk/_internal/sessions.py:969-975), which shows cycles are treated as possible input elsewhere in the codebase.

Minimal repro

Transcript with two progress entries pointing at each other, plus a user entry parented to one of them (single-entry variant: one progress entry whose parentUuid is its own uuid, parented-to by a user entry):

from claude_agent_sdk._internal import session_mutations

entries = [
    {"uuid": "A", "parentUuid": "B", "type": "progress", "timestamp": "2026-01-01T00:00:00Z"},
    {"uuid": "B", "parentUuid": "A", "type": "progress", "timestamp": "2026-01-01T00:00:00Z"},
    {"uuid": "U", "parentUuid": "A", "type": "user", "timestamp": "2026-01-01T00:00:00Z",
     "message": {"role": "user", "content": "hi"}},
]

# WARNING: this call does not return on the affected version.
session_mutations._build_fork_lines(entries, [], "sess", None, None, lambda: None)

(The same input reaches this walk through the public fork_session(session_id) / fork_session_via_store paths; the hang was demonstrated by driving _build_fork_lines directly, with the walk run in a bounded child process.)

Observed: the walk alternates A → B → A forever; the child process was still running at a 3s timeout (vs a <1ms acyclic baseline) and had to be terminated. Same result for the self-parenting variant.

Expected: the walk terminates — e.g. it breaks on a repeated parent id (mirroring walk_seen), returning a forked transcript instead of hanging.

Impact

A malformed or corrupted transcript (cyclic parentUuid links between progress entries) makes any fork of that session hang indefinitely with no error, blocking the calling thread. The fork operation has no timeout, so the caller cannot recover except by killing the thread/process. Local correctness/robustness impact only.

Suggested fix

Add a seen-set (or iteration bound) to the parentUuid walk in _build_fork_lines, breaking on a repeated parent id, mirroring the walk_seen guard in _build_conversation_chain.

Environment

  • Python 3.12.3, Linux
  • Verified against the pinned commit above; the acyclic baseline (user → progress → user) finishes in <1s with the leaf's parentUuid correctly remapped.
Lenguaje dominante
Python
Estrellas
8.5k
Forks
1.4k
Merge medio
3 d 10 h
PR fusionados (30 d)
13

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

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 anthropics/claude-agent-sdk-python

Todos los issues de anthropics/claude-agent-sdk-python

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.