diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 0bf10123..2927a100 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixed - Fixed `--model` resolution for authenticated custom model IDs whose slash prefix matches an unauthenticated built-in provider ([#5643](https://github.com/earendil-works/pi/issues/5643)). +- Fixed `/fork` to keep session parent chains connected when the forked path contains labels ([#5669](https://github.com/earendil-works/pi/issues/5669)). - Fixed `/share` and `/export` HTML exports to use the active fallback theme when the configured custom theme no longer exists ([#5596](https://github.com/earendil-works/pi/issues/5596)). - Fixed custom fallback model IDs with `:` suffixes to preserve the requested thinking level when the provider template model does not advertise reasoning ([#5552](https://github.com/earendil-works/pi/issues/5552)). diff --git a/packages/coding-agent/src/core/session-manager.ts b/packages/coding-agent/src/core/session-manager.ts index c2bae164..62942480 100644 --- a/packages/coding-agent/src/core/session-manager.ts +++ b/packages/coding-agent/src/core/session-manager.ts @@ -1290,8 +1290,16 @@ export class SessionManager { throw new Error(`Entry ${leafId} not found`); } - // Filter out LabelEntry from path - we'll recreate them from the resolved map - const pathWithoutLabels = path.filter((e) => e.type !== "label"); + // Filter out LabelEntry from path - we'll recreate them from the resolved map. + // Because labels are real tree entries, later entries can be children of labels; + // removing labels requires re-chaining the retained path to avoid orphaned subtrees. + const pathWithoutLabels: SessionEntry[] = []; + let pathParentId: string | null = null; + for (const entry of path) { + if (entry.type === "label") continue; + pathWithoutLabels.push({ ...entry, parentId: pathParentId }); + pathParentId = entry.id; + } const newSessionId = createSessionId(); const timestamp = new Date().toISOString(); diff --git a/packages/coding-agent/test/session-manager/labels.test.ts b/packages/coding-agent/test/session-manager/labels.test.ts index 7500746e..353454c7 100644 --- a/packages/coding-agent/test/session-manager/labels.test.ts +++ b/packages/coding-agent/test/session-manager/labels.test.ts @@ -142,6 +142,19 @@ describe("SessionManager labels", () => { expect(msg2Node?.labelTimestamp).toBe(msg2LabelEntry.timestamp); }); + it("rewires children of removed labels when forking", () => { + const session = SessionManager.inMemory(); + + const msg1Id = session.appendMessage({ role: "user", content: "hello", timestamp: 1 }); + session.appendLabelChange(msg1Id, "checkpoint"); + const modelChangeId = session.appendModelChange("anthropic", "claude-test"); + const msg2Id = session.appendMessage({ role: "user", content: "followup", timestamp: 2 }); + + session.createBranchedSession(msg2Id); + + expect(session.getEntry(modelChangeId)?.parentId).toBe(msg1Id); + }); + it("labels not on path are not preserved in createBranchedSession", () => { const session = SessionManager.inMemory();