Skip to content

Pi: fork from a turn fails when the fork target's session entry lives in a previous Pi session #28377

Closed

Description

@hong-tm

Area: apps/server (packages/provider-pi/src/server/adapter.ts)

Steps to reproduce

  1. Run a Pi thread with several real user turns to completion (turns 1–5, all strong nativeTurnRef).
  2. Have a Pi extension with background work in the session: each wake (monitor notification / goal continuation) gets adopted as a T3 continuation run with a synthetic user message. Each adopted turn keeps a weak synthetic nativeTurnRef permanently.
  3. Let the session restart at least once so the thread spans multiple Pi session files (e.g. idle-session release, server restart, or wake-driven re-spawn).
  4. Fork from the last real user turn (e.g. via a side chat).

Expected behavior

The fork succeeds: the forked session contains the target turn and discards the adopted-wake turns.

Actual behavior

Every fork from that turn fails permanently:

pi provider protocol error: Pi turn 6 has no captured session-tree entry and none was found in the session tree

(turn 6 = the first adopted-wake turn after the target)

Impact

Major degradation or frequent failure

Version or commit

v0.1.7-nightly.20261031.3991; same code on current main

Environment

Linux x64, Pi 2026.10.27-1, T3-launched Pi provider with a background-work extension (custom memory plugin) loaded

Logs or stack traces

pi provider protocol error: Pi turn 6 has no captured session-tree entry and none was found in the session tree

Analysis

State from statev2.sqlite (orchestration_v2_projection_provider_turns.payload_json):

turn ref
1,3,4,5 strong native ids (real user turns)
6–18 weak synthetic pending refs (adopted wakes)

Forking from turn 5:

  1. piForkBoundary picks turn 6 as the boundary (first later turn with a non-null ref); its ref is weak, so the boundary is uncaptured.
  2. piUncapturedForkEntry requires listing.entries.has(stopAt) where stopAt is turn 5's strong native id (adapter.ts:3266). Turn 5's entry lives in an earlier Pi session file; the live session's tree does not contain it, so the lookup returns undefined and piForkEntry throws ProviderAdapterProtocolError (adapter.ts:3238) with no further fallback.

This is a third variant the previous fixes did not cover:

Possible fixes

When stopAt is absent from the live session tree, fall back to loading the previous Pi session that contains the target's entry (it is on disk), or seed the fork from the projected thread history the way context handoff does, instead of failing closed.


AI-generated by @hong-tm's agent (opencode), posted under his instruction.

Activity

  1. StiensWout commented on Nov 9, 2026

    @StiensWout
    Contributor

    Note

    🤖 GPT 6.1 Sol responding on behalf of StiensWout

    The maintainer closed our independent #33375 and is retaining #28538 for the same previous-session fork failure, citing its smaller diff and native adapter-to-Pi verification. Our PR retains additional snapshot/history cases and explicit split-proof limits. Once a build contains the retained fix, could you retry the historical-session fork and close this issue if it preserves exactly the target history after that fix lands?

  2. juliusmarminge commented on Nov 11, 2026

    @juliusmarminge
    Member

    Fixed by #27729, now on main: When Pi cannot locate the fork boundary in its current session tree, the fork now recovers with projected history through the selected turn, excluding later wakes, instead of failing permanently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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