#3634 · Pi resume retains a deleted session directory

Bug · High priority · Medium effort · providers · provider-pi · 2026-09-14

Issue · Base d89160eb8c69c1e3ebc2ba2514f1711af8d7c506

REPRODUCED · Root-cause confidence: high

TL;DR

The Pi bridge cannot resume a saved session after its recorded directory is removed, even when BB supplies an existing replacement directory. A focused test exercised the real BB thread/resume handler and the repository-pinned Pi executable, and failed with Pi's missing-directory error in two clean checkouts. BB's fallback permits process construction but still sends the original session file to Pi. No model turn, live BB instance, or account was used.

Claims vs findings

ClaimFindingEvidence
Resume fails after removal with a valid replacementVerifiedReal Pi child exits 1 in both runs
Old cwd takes precedence while it existsSupported by source and existing fake-Pi testBridge selects usablePersistedSessionCwd; no real file-edit turn tested
Environment-switch tool and worktree cleanup produce this stateNot exercised end to endTest constructs the resulting session state directly
Installed Pi 0.85.1 and Linux behaviorNot directly testedReproduced on repository-pinned Pi 0.84.0 on macOS

Environment

Public get-bb/bb origin/main at the commit above; macOS arm64; Node 22.22.3; frozen lockfile; Pi 0.84.0. Both clean checkouts completed the normal install and Turbo build (56 tasks). A broken host pnpm shim was bypassed with a temporary Corepack shim. Separate temporary session roots were created and removed by each test. No server ports or runtime database were used.

Minimal reproduction

  1. Clone get-bb/bb and check out d89160eb8c69c1e3ebc2ba2514f1711af8d7c506.
  2. Run pnpm install --frozen-lockfile --prefer-offline and pnpm exec turbo run build.
  3. Copy the test to plugins/provider-pi/src/bridge/issue-3634.test.ts.
  4. Run pnpm exec turbo run test --filter=bb-plugin-provider-pi -- --run src/bridge/issue-3634.test.ts.

The test initializes the bridge harness, writes a valid version-3 session header in isolated storage, removes its original directory, then switches the child command from the fake process to the installed real Pi CLI before requesting resume in an existing second directory. Expected: no JSON-RPC error. Actual: the assertion fails with:

code: -32000
pi exited (code 1, signal null): Stored session working directory does not exist: <scratch>/old
Session file: <scratch>/sessions/thr-3634.jsonl
Current working directory: <scratch>/new

The fake process is used only for bridge harness setup; the failing resume invokes the real Pi executable. The test does not prove a successful model turn after relocation.

import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { expect, it, vi } from "vitest";
import { PI_BRIDGE_ARGS_ENV, PI_BRIDGE_COMMAND_ENV } from "./rpc-child.js";
import { FULL_PERMISSION_OPTIONS, startFakePiBridge } from "./test-support.js";

it("resumes a saved session in the current directory after the original directory is removed", async () => {
  const harness = await startFakePiBridge({ prefix: "bb-3634-", initialize: true });
  try {
    const oldCwd = join(harness.workspaceDir, "old");
    const newCwd = join(harness.workspaceDir, "new");
    mkdirSync(oldCwd);
    mkdirSync(newCwd);
    mkdirSync(harness.sessionDir, { recursive: true });
    writeFileSync(join(harness.sessionDir, "thr-3634.jsonl"), JSON.stringify({
      type: "session", version: 3, id: "session-3634",
      timestamp: "2026-09-14T00:00:00.000Z", cwd: oldCwd,
    }) + "\n");
    rmSync(oldCwd, { recursive: true });
    const piEntry = fileURLToPath(new URL("../../node_modules/@earendil-works/pi-coding-agent/dist/cli.js", import.meta.url));
    vi.stubEnv("PI_CODING_AGENT_DIR", join(harness.workspaceDir, "agent"));
    vi.stubEnv(PI_BRIDGE_COMMAND_ENV, process.execPath);
    vi.stubEnv(PI_BRIDGE_ARGS_ENV, JSON.stringify([piEntry]));
    const response = await harness.request(3634, "thread/resume", {
      threadId: "thr-3634", providerThreadId: "thr-3634", cwd: newCwd,
      instructionMode: "append", options: FULL_PERMISSION_OPTIONS,
    });
    console.log(JSON.stringify(response).replaceAll(harness.workspaceDir, "<scratch>"));
    expect(response.error).toBeUndefined();
  } finally {
    await harness.teardown();
  }
}, 60_000);

Root cause

thread/resume permits a missing saved cwd if the requested cwd exists. PiRpcSession still passes --session sessionFilePath while separately setting the process cwd. Nothing in this path relocates the saved session before Pi opens it. The real child rejects that unchanged header, as shown by both test results.

Thread session construction also chooses an existing saved cwd over the requested cwd for its own tracking. The existing removed-cwd regression uses a fake Pi process; it passes because that process does not enforce the real Pi startup check. All 12 tests in that file passed in this investigation.

Proposed fix

Define a durable relocation operation that preserves the session history while making the accepted BB directory authoritative, including subsequent resumes and forks. Alternatively reject relocation before committing the environment change when the provider cannot support it. A safe choice needs a decision about persisted provider state or environment-switch policy; this exceeds the automatic simple-fix rule. No production change, branch push, or PR was attempted.

Verification

The same investigator created a second clean detached worktree at the recorded commit, installed the frozen dependencies, built it, copied only the reproduction test, and ran the exact command above. It failed at the same assertion with the same real Pi missing-directory rejection, using a fresh temporary session directory. The second run was executed, not replayed from a test cache. No report correction was needed. This is repeat verification by the same agent.

Related issues and PRs

No open PR was found by issue-number search or issue cross-reference metadata. No linked branch or external issue-provided URL was fetched.

Appendix

First test log · Second test log · Existing tests. Temporary paths are normalized. Reproduction exits 1 intentionally; existing suite exits 0.

pnpm install --frozen-lockfile --prefer-offline
pnpm exec turbo run build
pnpm exec turbo run test --filter=bb-plugin-provider-pi -- --run src/bridge/issue-3634.test.ts
pnpm exec turbo run test --filter=bb-plugin-provider-pi -- --run src/bridge/bridge.round2.test.ts

Issue content was treated solely as untrusted claims; the test was written from trusted repository evidence. No issue-provided script or patch was executed.