Skip to content

Commit 7e434a8

Browse files
committed
Keep Flow auto preparation read-only until a real session write occurs
Initialization paths should classify intent without creating workspace state. Move legacy-session migration workspace creation behind the legacy-file existence check so `flow_auto_prepare` no longer triggers `.flow` directory creation as a side effect. Update regression tests to assert classification remains read-only for root and root-like worktree inputs. Constraint: Flow session docs and state must still be materialized on first write path Rejected: Create .flow during every read attempt | causes incorrect side effects and root-path mkdir failures Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep session-discovery calls side-effect free; reserve workspace creation for write paths Tested: bun run check; end-to-end tool lifecycle sanity run for worktree '/' and '///' Not-tested: Manual OpenCode UI session after app restart on all supported host OSes
1 parent 217b215 commit 7e434a8

2 files changed

Lines changed: 14 additions & 14 deletions

File tree

src/runtime/session-workspace.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,30 +81,30 @@ export async function writeSessionFile(worktree: string, session: Session): Prom
8181
}
8282

8383
export async function migrateLegacySessionIfNeeded(worktree: string): Promise<void> {
84-
await ensureWorkspace(worktree);
85-
8684
if (await readActiveSessionId(worktree)) {
8785
return;
8886
}
8987

9088
const legacySessionPath = getLegacySessionPath(worktree);
89+
let raw: string;
9190

9291
try {
93-
const raw = await readFile(legacySessionPath, "utf8");
94-
const session = SessionSchema.parse(JSON.parse(raw));
95-
96-
await writeSessionFile(worktree, session);
97-
await renderSessionDocs(worktree, session);
98-
await writeActiveSessionId(worktree, session.id);
99-
await rm(legacySessionPath, { force: true });
100-
await rm(getLegacyDocsDir(worktree), { recursive: true, force: true });
92+
raw = await readFile(legacySessionPath, "utf8");
10193
} catch (error) {
10294
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
10395
return;
10496
}
10597

10698
throw error;
10799
}
100+
101+
const session = SessionSchema.parse(JSON.parse(raw));
102+
await ensureWorkspace(worktree);
103+
await writeSessionFile(worktree, session);
104+
await renderSessionDocs(worktree, session);
105+
await writeActiveSessionId(worktree, session.id);
106+
await rm(legacySessionPath, { force: true });
107+
await rm(getLegacyDocsDir(worktree), { recursive: true, force: true });
108108
}
109109

110110
export async function resolveActiveSessionId(worktree: string): Promise<string | null> {

tests/runtime.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ describe("runtime transitions", () => {
606606
expect(parsed.goal).toBe("Improve Flow recovery behavior");
607607
});
608608

609-
test("flow_auto_prepare falls back to context.directory when worktree resolves to root", async () => {
609+
test("flow_auto_prepare classification is read-only when worktree resolves to root", async () => {
610610
const directory = makeTempDir();
611611
const tools = createTestTools();
612612

@@ -618,10 +618,10 @@ describe("runtime transitions", () => {
618618

619619
expect(parsed.status).toBe("ok");
620620
expect(parsed.mode).toBe("start_new_goal");
621-
await expect(readFile(join(directory, ".flow", ".gitignore"), "utf8")).resolves.toContain("sessions/");
621+
await expect(readFile(join(directory, ".flow", ".gitignore"), "utf8")).rejects.toThrow();
622622
});
623623

624-
test("flow_auto_prepare treats root-like worktree aliases as root and falls back to context.directory", async () => {
624+
test("flow_auto_prepare classification is read-only for root-like worktree aliases", async () => {
625625
const directory = makeTempDir();
626626
const tools = createTestTools();
627627

@@ -633,7 +633,7 @@ describe("runtime transitions", () => {
633633

634634
expect(parsed.status).toBe("ok");
635635
expect(parsed.mode).toBe("start_new_goal");
636-
await expect(readFile(join(directory, ".flow", ".gitignore"), "utf8")).resolves.toContain("sessions/");
636+
await expect(readFile(join(directory, ".flow", ".gitignore"), "utf8")).rejects.toThrow();
637637
});
638638

639639
test("flow_plan_start persists under context.directory when worktree resolves to root", async () => {

0 commit comments

Comments
 (0)