feat(run-action): forceReload flag acknowledges human edits as new baseline (closes #173 sub-issue 3)#176
Merged
Conversation
…seline Closes #173 (sub-issue 3). Symptom: cdp_run_action refuses to complete with "yaml at ... has been edited externally since the in-memory action was loaded" every time the human touches the YAML during composition. The Phase 129 guardrail in cdp_repair_action — added to protect offline human edits from auto-repair clobber — can't distinguish "human edited offline, protect" from "human is actively composing, use latest". Forces users to fall back to raw maestro_run, losing auto-repair telemetry. Fix: new `forceReload: boolean` arg on cdp_run_action (default true). When true, the orchestrator stats the YAML on load and persists its current mtime to the sidecar's lastSeenMtimeMs BEFORE running. Any downstream actionWasEditedExternally() check returns false, so repair proceeds normally on top of the human's latest edit. Pass `forceReload: false` to opt back into the strict Phase 129 behavior (refuse to persist over an externally-modified YAML). The right choice for CI replays of fixed baselines and any flow where offline-edit protection matters more than active-composition ergonomics. Why default true: the high-frequency friction is the active-composition case. The protection case is the minority. Defaulting true fixes the reported pain without requiring callers to remember a flag every call. codex-pair flagged this design call as a HIGH during review; it's a deliberate user-approved tradeoff (the strict behavior is opt-in for callers who specifically need it). Implementation: - new acknowledgeExternalEdit(action) helper in action-store.ts — stats YAML, persists markSeen(state, currentMtime) to sidecar, returns refreshed ReusableAction. No-op when mtime is already current. - cdp_run_action handler invokes it iff forceReload !== false, between loadAction() and the maestroRun call. - new Zod field + extended tool description so MCP clients see the flag and understand the tradeoff. Tests (1471/1471 passing, +4 net new): - acknowledgeExternalEdit: refreshes sidecar lastSeenMtimeMs to YAML's current stat-mtime - acknowledgeExternalEdit: no-op when sidecar already current (returns same object reference, no disk write) - cdp_run_action forceReload=true (default): the sidecar advance happens BEFORE the run starts (snapshot captured inside fake maestroRun) - cdp_run_action forceReload=false: the sidecar is still at baseline at the moment maestro runs (snapshot proves no pre-run acknowledgment), and the strict mode run fails with isError=true on stale YAML The post-run mtime check was strengthened to a mid-run snapshot per codex-pair's MED review — the prior assertion could pass even if acknowledgment never happened (the RunRecord save would advance mtime anyway). The new snapshot capture proves the pre-run acknowledgment specifically. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the third sub-issue in #173 (fresh session feedback) —
cdp_run_actionaborting with "yaml at ... has been edited externally since the in-memory action was loaded" during active YAML composition.Root cause
cdp_repair_action's Phase 129 guardrail (actionWasEditedExternally()check) was designed to protect offline human edits from auto-repair clobber. But it has no way to distinguish:In active composition, the guardrail fires constantly (every YAML save bumps mtime past the sidecar's
lastSeenMtimeMs) and forces fallback to rawmaestro_run, losing auto-repair telemetry.Fix
New
forceReload: booleanarg oncdp_run_action(defaulttrue).forceReload: true(default) — the orchestrator stats the YAML at load time and persists its current mtime to the sidecar'slastSeenMtimeMsbefore running. DownstreamactionWasEditedExternally()returns false; repair proceeds normally on top of the human's latest edit.forceReload: false— opt-in to the strict Phase 129 behavior. Refuses to persist over an externally-modified YAML. The right choice for CI replays of fixed baselines.Why default
trueThe active-composition case is high-frequency in the user's reported session. The protection case is the minority. Defaulting true fixes the reported pain without requiring callers to remember a flag every call.
Worth flagging: codex-pair raised this design call as a HIGH during review ("default forceReload bypasses external-edit protection before auto-repair"). It's a deliberate user-approved tradeoff — the strict behavior is opt-in for callers who specifically need offline-edit protection, and the tool description spells out the choice. Reviewers please weigh in if you disagree with the default.
Implementation
acknowledgeExternalEdit(action)helper indomain/action-store.ts— stats YAML, persistsmarkSeen(state, currentMtime)to sidecar, returns refreshedReusableAction. No-op when the YAML mtime is already current.cdp_run_actionhandler invokes it iffforceReload !== false, betweenloadAction()and the firstmaestroRuncall.Test plan
test/unit/gh-173-run-action-force-reload.test.js)acknowledgeExternalEdithelper: refreshes sidecar to YAML's current stat-mtimeacknowledgeExternalEdithelper: no-op when sidecar already current (returns same object reference, no disk write)maestroRun). A regression that skipped pre-run acknowledgment would still see the post-run RunRecord save advance the mtime, so this stronger assertion catches the exact failure mode.isError: trueon stale YAML — proves the user-facing pain is real and that opting out preserves it.cdp_run_actionwithout explicitly passingforceReload, observing the run + repair complete withoutSTALE_TARGETOut of scope
runFlow.whenrace,cdp_run_actionCAS auto-reload-and-retry was THIS PR's surface, Expo Dev Client tutorial modal trap).Refs
scripts/cdp-bridge/src/domain/action-store.ts:acknowledgeExternalEditscripts/cdp-bridge/src/tools/run-action.ts(handler wiring)scripts/cdp-bridge/src/tools/repair-action.ts:194🤖 Generated with Claude Code