mirror of
https://github.com/stablyai/orca.git
synced 2026-10-09 00:02:39 +00:00
Destructive worktree removal proves every PTY is dead before touching the filesystem. When a stop RPC failed, it re-listed the provider to check whether the PTY had already exited — but on the same deadline the sweeps had just spent, so it timed out without ever asking and read "could not verify" as "still live". The sweep spends that budget every run, making the refusal deterministic; --force never reached the gate, so the workspace was unremovable forever. - Verification gets its own budget instead of an exhausted remainder. - Verdicts split into exited / live / unverifiable; the error names the blocking PTY ids and why. - A reachable escape hatch: allowUnverifiedPtyStop, set only by genuine Force Delete affordances and the CLI's --force — never by the force the ordinary delete confirmation already sets — with an 'unstopped-pty' classifier reason so the desktop actually offers the button. - Force also survives a sweep that cannot complete; the non-force path still fails fast. Fixes #11960
55 lines
1.6 KiB
TypeScript
55 lines
1.6 KiB
TypeScript
/**
|
|
* Races `run()` against an absolute deadline (epoch ms).
|
|
*
|
|
* Without `failClosedError` the call is best-effort: a timeout or a rejection
|
|
* resolves to `fallback`. With it, both surface as a rejection so destructive
|
|
* callers can block on unproven work — `failClosedOnRunError` narrows which
|
|
* rejections count (some are benign sentinels).
|
|
*/
|
|
export async function settleBeforeDeadline<T>(
|
|
run: () => Promise<T>,
|
|
fallback: T,
|
|
deadline: number,
|
|
failClosedError?: Error,
|
|
failClosedOnRunError: (error: unknown) => boolean = () => true
|
|
): Promise<T> {
|
|
const remaining = deadline - Date.now()
|
|
if (remaining <= 0) {
|
|
if (failClosedError) {
|
|
throw failClosedError
|
|
}
|
|
return fallback
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
let settled = false
|
|
const finish = (value: T): void => {
|
|
if (settled) {
|
|
return
|
|
}
|
|
settled = true
|
|
clearTimeout(timer)
|
|
resolve(value)
|
|
}
|
|
const fail = (error: unknown): void => {
|
|
if (settled) {
|
|
return
|
|
}
|
|
settled = true
|
|
clearTimeout(timer)
|
|
reject(error)
|
|
}
|
|
const timer = setTimeout(
|
|
() => (failClosedError ? fail(failClosedError) : finish(fallback)),
|
|
remaining
|
|
)
|
|
timer.unref?.()
|
|
// Why: `.then(run)` rather than `run()` so a synchronous throw is routed
|
|
// through the same fail-closed filter instead of escaping the executor.
|
|
void Promise.resolve()
|
|
.then(run)
|
|
.then(finish, (error: unknown) =>
|
|
failClosedError && failClosedOnRunError(error) ? fail(error) : finish(fallback)
|
|
)
|
|
})
|
|
}
|