{"record":{"id":"eef2da2664d9e632","repo":"langchain-ai/deepagents","slug":"precompact-requires-a-stable-tool-call-identity","errorCode":null,"errorMessage":"PreCompact requires a stable tool-call identity","messagePattern":"PreCompact requires a stable tool-call identity","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/hooks/server_middleware.py","lineNumber":1100,"sourceCode":"        | PostToolUseFailureEvent\n        | PreCompactEvent\n        | StopEvent\n        | SubagentStartEvent\n        | SubagentStopEvent\n    ),\n    *,\n    logical_event_id: str | None = None,\n) -> str:\n    if isinstance(\n        event,\n        PreToolUseEvent | PostToolUseEvent | PostToolUseFailureEvent,\n    ):\n        return event.call.id\n    if isinstance(event, PreCompactEvent):\n        if logical_event_id:\n            return logical_event_id\n        msg = \"PreCompact requires a stable tool-call identity\"\n        raise ValueError(msg)\n    if isinstance(event, SubagentStartEvent):\n        return event.agent.id\n    if isinstance(event, SubagentStopEvent):\n        return f\"{event.agent.id}:{event.continuation_count}\"\n    message_hash = hashlib.sha256(event.last_assistant_message.encode()).hexdigest()\n    return f\"{event.continuation_count}:{message_hash}\"\n\n\ndef _config_thread_id(config: Mapping[str, Any] | None) -> str | None:\n    if not isinstance(config, Mapping):\n        return None\n    configurable = config.get(\"configurable\")\n    if not isinstance(configurable, Mapping):\n        return None\n    value = configurable.get(\"thread_id\")\n    return value if isinstance(value, str) and value else None\n\n","sourceCodeStart":1082,"sourceCodeEnd":1118,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/hooks/server_middleware.py#L1082-L1118","documentation":"Hook invocation ids must be stable across retries so events can be deduplicated. PreCompactEvent has no tool call to derive an id from, so it depends on a caller-supplied logical_event_id; when neither a tool-call id nor a logical id exists, _logical_event_identity raises ValueError because no stable identity can be formed.","triggerScenarios":"A PreCompactEvent reaches _invocation_id/_logical_event_identity with logical_event_id=None and no other stable key: triggering pre-compact hooks through a path that doesn't assign a logical event id (e.g. a manual or custom compaction trigger outside the standard compact flow).","commonSituations":"Custom compaction pipelines firing PreCompactEvent without generating a logical_event_id; replay/retry logic that drops the logical id; tests constructing PreCompactEvent directly and invoking the middleware.","solutions":["Pass a logical_event_id when invoking pre-compact hooks — derive it from the compaction request (e.g. a uuid or thread/turn composite key).","Use the standard compaction flow, which assigns the logical id automatically.","If building events in tests, supply event.call.id or a logical_event_id to satisfy identity computation.","Catch ValueError and treat it as a programming error: regenerate the event with a stable id rather than retrying."],"exampleFix":"// before\nawait middleware._invoke_hook(gate, PreCompactEvent(...), config, deadline)  # ValueError\n\n// after\nawait middleware._invoke_hook(\n    gate, PreCompactEvent(...), config, deadline,\n    logical_event_id=f\"compact:{thread_id}:{turn}\",\n)","handlingStrategy":"validation","validationCode":"if isinstance(event, PreCompactEvent) and not logical_event_id:\n    raise ValueError(\"pass logical_event_id for PreCompact hook invocations\")","typeGuard":null,"tryCatchPattern":"try:\n    run_compact_hooks(event, ...)\nexcept ValueError as exc:\n    if \"stable tool-call identity\" in str(exc):\n        rerun_with_logical_event_id()","preventionTips":["Always supply logical_event_id for PreCompactEvent invocations","Use the built-in compaction flow which assigns ids automatically","Generate ids from stable keys (thread id + turn), not random per retry","Cover pre-compact hooks in tests with an explicit logical id"],"tags":["hooks","identity","value-error"],"backgroundTag":"missing-stable-event-identity","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}