From 01ae99e426e2211e583c270e4d4d6b962c932eaf Mon Sep 17 00:00:00 2001 From: Kostandin Angjellari Date: Thu, 23 Jul 2026 19:43:15 +0200 Subject: [PATCH 1/2] FE-1261: Refresh state after Petri preparation --- src/executor/__tests__/orchestrate.test.ts | 24 ++++++++++++++++++++++ src/executor/orchestrate.ts | 5 ++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/executor/__tests__/orchestrate.test.ts b/src/executor/__tests__/orchestrate.test.ts index 8b53cd527..c4697783e 100644 --- a/src/executor/__tests__/orchestrate.test.ts +++ b/src/executor/__tests__/orchestrate.test.ts @@ -3623,6 +3623,30 @@ describe('drive', () => { }); }); + it('refreshes prepared metadata before reconciling a direct lifecycle step', async () => { + const cwd = await mkdtemp(join(tmpdir(), 'brunch-drive-preparation-refresh-')); + await createRunAtCreated(cwd, ['task-1']); + await createWorktree({ cwd, runId: 'run-1', gitWorktree: createFakeGitWorktreePort() }); + + await expect( + drive({ cwd, runId: 'run-1', ports: fakePorts() }, linearScheduler, serialFiringPolicy, { + maxFirings: 1, + }), + ).resolves.toEqual({ + status: 'completed', + runStatus: 'worktree_populated', + }); + await expect(readRunMetadata(runMetadataPath(cwd, 'run-1'))).resolves.toMatchObject({ + status: 'worktree_populated', + petriObservationPrepared: true, + }); + expect( + (await readPetriEvents(cwd)).flatMap((event) => + event.kind === 'transition_fired' ? [event.transitionId] : [], + ), + ).toEqual(['worktree_create', 'populate']); + }); + it('halts and repairs when lifecycle catch-up cannot persist its marking', async () => { const cwd = await mkdtemp(join(tmpdir(), 'brunch-drive-reconcile-marking-failure-')); await createRunAtCreated(cwd, ['task-1']); diff --git a/src/executor/orchestrate.ts b/src/executor/orchestrate.ts index 228a83d9c..f10eb008c 100644 --- a/src/executor/orchestrate.ts +++ b/src/executor/orchestrate.ts @@ -420,7 +420,7 @@ async function driveOwned( // unchanged is treated as stuck. Replace with per-step outcome classification // if steps gain retry/abort semantics beyond advance-or-hold. driveLoop: for (;;) { - const state = await readRunMetadata(metadataPath); + let state = await readRunMetadata(metadataPath); if (!state) return { status: 'missing_run', runId: ctx.runId }; if (options.maxFirings !== undefined && firedTransitions >= options.maxFirings) { return { status: 'completed', runStatus: state.status }; @@ -448,6 +448,9 @@ async function driveOwned( observationPreparationAttempted = true; try { plan = await preparePetriObservation({ cwd: ctx.cwd, runId: ctx.runId }); + const preparedState = await readRunMetadata(metadataPath); + if (!preparedState) return { status: 'missing_run', runId: ctx.runId }; + state = preparedState; } catch (error) { const terminal = classifyDriveTerminal({ kind: 'step_halted', From 25617164f2e0a793f64028f676a3436498de8587 Mon Sep 17 00:00:00 2001 From: Kostandin Angjellari Date: Thu, 23 Jul 2026 19:49:55 +0200 Subject: [PATCH 2/2] FE-1261: Record prepared-state repair release --- .changeset/refresh-prepared-petri-state.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/refresh-prepared-petri-state.md diff --git a/.changeset/refresh-prepared-petri-state.md b/.changeset/refresh-prepared-petri-state.md new file mode 100644 index 000000000..480b27d5e --- /dev/null +++ b/.changeset/refresh-prepared-petri-state.md @@ -0,0 +1,5 @@ +--- +"@hashintel/brunch": patch +--- + +Refresh prepared run metadata before reconciling direct lifecycle steps.