Skip to content

fix(lab): ensure manual Lab runs are cancellable during shutdown - #288

Open
luvs01 wants to merge 8 commits into
devfrom
codex/propose-fix-for-lab-shutdown-issue
Open

luvs01 wants to merge 8 commits into
devfrom
codex/propose-fix-for-lab-shutdown-issue

Conversation

@luvs01

@luvs01 luvs01 commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Summary

  • Prevent manual /api/lab/automation/run executions from continuing past drainAndShutdown when Lab was never activated, by ensuring manual runs register a shutdown hook so they are cancelled like other Lab activities.
  • Register a run-scoped optional shutdown hook for manual dispatches in src/lab/automation/orchestrator.ts using registerOptionalShutdownHook(...), detached in a finally so the hook cannot leak after completion; a completed sweep is treated as already fired — the run is refused instead of queued.
  • Close the same race on the scheduler path (Devin Review follow-up): startLabAutomationScheduler now gates on didRunOptionalShutdownHooks() — a policy PUT or CLI enable resuming after the sweep can no longer register an orphaned hook, reset shutdownRequested, and leave a live interval dispatching Lab work outside the completed sweep.
  • Clarify hooksRan as a process-lifetime latch: production drainAndShutdown callers exit or hand off to a new OS process, so there is no in-process restart; resetOptionalShutdownHooksForTests models the fresh process.
  • Outcome-level regression tests cover the post-sweep manual run, the post-sweep scheduler start (applySchedulerPolicy path), and the test-reset re-arm equivalents for both.

Verification

  • bun run typecheck — clean.
  • bun test --isolate tests/lib/optional-shutdown-hooks.test.ts tests/lab/lab-automation-review-regressions.test.ts — 28 pass.
  • bun test --isolate over tests/lab/lab-automation.test.ts, lab-activation.test.ts, lab-automation-ingwannu-regressions.test.ts, lab-automation-management-http.test.ts, core-lab-boundary.test.ts, coderabbit/persisted-cap regression files — all pass; only a pre-existing unnamed afterEach hook timeout in lab-activation.test.ts reproduces on Windows even without this change.
  • bun run privacy:scan, bun run structure:check — pass.
  • The pre-push hook's full parallel suite exceeded its 900s lane budget on this Windows box (killed mid-run); focused and lab-domain suites above all pass under the CI-equivalent --isolate mode.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Codex Task

Link to Devin session: https://app.devin.ai/sessions/cfc5342a3c4d4b7eb9c1b57b2adca7c6
Open in Devin Desktop: https://app.devin.ai/desktop/session/cfc5342a3c4d4b7eb9c1b57b2adca7c6?variant=devin
Requested by: @luvs01


Devin Review

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Repository: luvs01/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 590baee3-318c-46d1-b03c-ff3fde6b794f


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Aug 15, 2026
@github-actions github-actions Bot changed the title fix(lab): ensure manual Lab runs are cancellable during shutdown [WRONG BRANCH] fix(lab): ensure manual Lab runs are cancellable during shutdown Aug 15, 2026
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown

✅ READY

  • all PR quality gates passed.

Hygiene

Deterministic PR hygiene checks passed.

@github-actions
github-actions Bot marked this pull request as draft August 15, 2026 02:45
@luvs01 luvs01 closed this Aug 19, 2026
@luvs01 luvs01 reopened this Aug 19, 2026
@luvs01
luvs01 force-pushed the codex/propose-fix-for-lab-shutdown-issue branch from 37b3d5b to bf4e97d Compare August 19, 2026 05:40
@luvs01 luvs01 changed the title [WRONG BRANCH] fix(lab): ensure manual Lab runs are cancellable during shutdown fix(lab): ensure manual Lab runs are cancellable during shutdown Sep 3, 2026
@luvs01
luvs01 changed the base branch from main to dev September 3, 2026 06:24
@github-actions
github-actions Bot marked this pull request as ready for review September 3, 2026 06:33
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 3 commits September 21, 2026 04:01
…lab-shutdown-issue

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

# Conflicts:
#	tests/lab/lab-automation-review-regressions.test.ts
The management route sits outside the data-plane drain gate, so a request
accepted before listener teardown can call enqueueManualLabRun after
runOptionalShutdownHooks already snapshotted the registry. The per-run
hook it then registers is never invoked, and on an install where the Lab
was never activated nothing had set shutdownRequested either, so the
dispatch could continue past drainAndShutdown.

The registry now reports whether the sweep ran; enqueueManualLabRun
refuses post-sweep enqueues outright and re-checks after registering so
a run that raced the snapshot still has its teardown applied inline.

Co-Authored-By: Epinephrine <luvs01@hanmail.net>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown

Already addressed at head by 9e453a9:

  • optional-shutdown-hooks.ts now keeps a process-lifetime hooksRan latch set at the start of runOptionalShutdownHooks and reset only in resetOptionalShutdownHooksForTests.
  • enqueueManualLabRun gates on didRunOptionalShutdownHooks(): a request reaching it after the sweep is refused outright (no queue, no dispatch), and it re-checks after registering so a registration racing the snapshot runs its teardown inline — equivalent to retro-applying the hook.
  • Added regression test a manual run arriving after the shutdown sweep does not queue or dispatch (passing) covering the paused-request-then-sweep scenario.

A policy PUT (or CLI automation enable) resuming after
runOptionalShutdownHooks() bypassed the manual-run-only gate added in
9e453a9: startLabAutomationScheduler registered an orphaned hook,
reset shutdownRequested to false, and left a live interval dispatching
Lab work outside the completed sweep.

startLabAutomationScheduler now mirrors enqueueManualLabRun: an entry
gate on didRunOptionalShutdownHooks() requests shutdown and returns, and
a post-registration re-check keeps the latch set instead of starting a
timer the completed snapshot cannot reach. The registration stays live
so a repeat sweep still tears the scheduler down.

Also clarifies the ANALYSIS finding: hooksRan is process-lifetime —
every production drainAndShutdown caller exits or hands off to a new OS
process, so there is no in-process restart; the test reset models one.
Adds matching manual-run coverage (rejected after the sweep, dispatched
again after a test reset) and an outcome-level regression test driving
the applySchedulerPolicy path.

Co-Authored-By: Epinephrine <luvs01@hanmail.net>
@devin-ai-integration

Copy link
Copy Markdown

Fixed in e46307f.

startLabAutomationScheduler now gates on didRunOptionalShutdownHooks() exactly like enqueueManualLabRun: an entry check requests shutdown and returns before the owner/existing-timer logic, and a post-registration re-check keeps shutdownRequested set instead of arming a timer the completed snapshot cannot reach. The hook registration stays live so a repeat sweep still tears the scheduler down. This covers every start path — applySchedulerPolicy (late policy PUT), cli/lab.ts automation enable, and startAutomationIfEnabled — not just the manual-run path.

Regression coverage added:

  • a scheduler start after the shutdown sweep is refused (tests/lib/optional-shutdown-hooks.test.ts)
  • a policy update landing after the shutdown sweep cannot restart Lab automation (tests/lab/lab-automation-review-regressions.test.ts) — drives the reconcile+start path after a sweep and asserts no timer exists and a manual tick dispatches nothing.

@devin-ai-integration

Copy link
Copy Markdown

Clarified in e46307f: production has no in-process restart after a sweep. Both drainAndShutdown call sites end in process.exit (management stop in management-api.ts, restart handoff in system-restart.ts spawns a detached child and exits the parent), and shutdownDraining is already documented as a process-lifetime latch. hooksRan staying true until the test reset is therefore correct — the reset models a fresh process, and the doc comment on registerOptionalShutdownHook now states this explicitly.

With that semantics settled, the scheduler side now behaves like the manual path: the previous "a scheduler restarted after shutdown is stoppable again" test (which assumed a live post-sweep start) is split into a scheduler start after the shutdown sweep is refused and a scheduler restarted after a test reset is stoppable again. Matching manual-run coverage added: manual runs stay rejected after the sweep until a test reset re-arms them asserts a post-sweep enqueueManualLabRun returns null and that a full test reset (hooks + scheduler state) lets the next manual run dispatch — the in-process-restart equivalent for the manual path.

@devin-ai-integration

Copy link
Copy Markdown

E2E verification of the Lab automation golden path — ran this branch as a real foreground server (bun run src/cli/index.ts start --port 10555, scratch OPENCODEX_HOME, x-opencodex-api-key admin auth) and exercised the management API end-to-end on Windows. The new didRunOptionalShutdownHooks gates do not affect normal operation:

  • GET /api/lab/automation → 401 without token / 200 with token; baseline schedulerRunning:false, enabled:false
  • PUT /api/lab/automation {enabled:true, protocolConformance:true} → 200 schedulerRunning:true, persists on re-GET (live interval through the new gate)
  • POST /api/lab/automation/run responses-core.protocol.request-shape → 200 state:"completed", terminalCode:"pass", trigger:"manual" (synchronous)
  • After the 60s tick: 28 trigger:"scheduled" runs enqueued; 3 completed pass, run budget consumed — scheduler genuinely dispatches
  • PUT {enabled:false}schedulerRunning:false; single Ctrl+C → clean drain, port closed ~10s

One scheduled run codex-core.protocol.apply-patch-turn ended failed/dispatch_failure — a harness-level failure on a CLI-less test box, unrelated to this change.

PUT enable → schedulerRunning:true
Manual run completed pass

More screenshots

401 + baseline
Scheduled runs after tick
Disable → schedulerRunning:false
Clean Ctrl+C drain

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

aardvark bug Something isn't working codex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant