Problem `startConnectionSync` persists a non-deferred run with status `queued` and no `workflowRunId`, sets `connection.syncStatus="queued"`, and only then starts the workflow. If the serverless function crashes or times out in that window, the orphan run projects as in-flight forever (`projectSyncRunStatus` returns `queued` verbatim when there is no workflow to look up), so every subsequent enqueue — manual, repair, hourly cron — resolves strategy `already_queued` against it. The connection wedges permanently. This is the non-deferred twin of the deferred-run wedge fixed in #103. Fix
•**`isOrphanedQueuedRun`** (sync-cutover-logic): a non-deferred `queued` run with no workflow attached, older than `STALE_SYNC_LOCK_MIN_AGE_MS` (90s, moved here and exported so service + tests share one floor), is abandoned — the start window has long since passed.
•**`isInFlightSyncRun`** takes optional `ageMs`/`orphanedQueuedMinAgeMs` and excludes stale orphans; age-less callers keep the conservative reading. The orphan predicate's params are required, so a caller can't silently defuse the check by omitting one.
•**`findActiveRunForConnection`** passes each candidate's age, and when the scan finds nothing in flight, terminalizes collected orphans via `recordSyncFailure({ terminalReason: "workflow_start_orphaned", skipDeferredStart: true })` — run record, connection projection, and lawful-sync mirror all terminalize together, and `skipDeferredStart` prevents recursion. Terminalization is deferred to after the scan so a failure is never stamped over a genuinely active run's connection state.
Known trade-off A workflow start that is merely slow (>90s between `putSyncRun` and the workflow attach) is indistinguishable from a crash and could be terminalized by a concurrent enqueue; `updateSyncRun` has no compare-and-swap, so the original could later flip the run back to running. Same acceptance as the existing `staleSyncLockBefore` recovery — the 90s floor comfortably exceeds a step's execution budget. A leased/conditional write on the queued record would close this class entirely (follow-up). Testing
•New `tests/integration/sync-orphaned-queued-run.integration.test.ts` in the style of the deferred-lifecycle test: stale orphan excluded and next enqueue resolves `queue_immediate` (mirroring production's `runId`/status strategy wiring); fresh in-window orphan still blocks duplicates; age never demotes runs with a workflow or deferred placeholders; age-less callers unchanged.
•Full integration suite: 173/173 pass.
Stacked on #106 (CI fix); the first two commits disappear from this diff once #106 merges. 🤖 Generated with [Claude Code](https://claude.com/claude-code)