Skip to content

fix(native): stop a restart on the first Ctrl-C at any phase - #646

Open
roodboi wants to merge 2 commits into
nextfrom
claude/hack-1953-restart-sigint
Open

roodboi wants to merge 2 commits into
nextfrom
claude/hack-1953-restart-sigint

Conversation

@roodboi

@roodboi roodboi commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The first Ctrl-C (SIGINT) or SIGTERM now stops a native hack restart at any phase. Before this change, a native restart installed no stop handler of its own until its new foreground start did. A first SIGINT during preflight, cleanup of the old run or the early start was therefore either:

  • dropped, when SIGINT was inherited as ignored, as it is for a background job in a non-interactive shell. This is the reported symptom: the restart ran to ready and needed a second Ctrl-C. Or:
  • fatal to the frontend mid-restart, which left the restart lock and intent behind.

A debug trace on 5.0.0-next.11 confirmed the cause. A SIGINT 2 s into a restart arrived during the old run's cleanup, and no process listener existed at that point.

Behavior

The restart now handles stop requests from its first step. When the new start installs its own handlers (nativeForegroundStopSignals), the restart hands stop requests over to them and removes its own. The first stop request cancels at the next safe point:

When Result
preflight ends with nothing stopped (Native restart canceled before cleanup; nothing was stopped.)
cleanup of the old run the owned stop runs to completion, the old frontend's finalizers are awaited, the new run is not started
start, before hand-off the start is abandoned before any hook, runtime start or service
start, after hand-off the start stops its own run as an interrupted up does

Every cancel:

  • prints one Stopping the native project; waiting for owned cleanup… line, then the confirmed-stop line, the same output as Ctrl-C after up;
  • removes the restart intent, so a plain hack up resumes the retained data instead of reporting a pending restart;
  • exits 130, or 143 for SIGTERM.

Sometimes a stop request arrives after the hand-off but before the start has requested any service, for example while it discovers host dependencies or waits for runtime up. If the start's own cleanup then completes, it ends with NativeStartupCanceledError (beforeServices). The restart treats that as a confirmed cancellation and retires the intent. Any other failure keeps the pending restart, including a cleanup failure, and so does a start that fails without a stop request.

startNativeProject gains an onStopSignals callback, called once its own handlers are installed. This is the hand-off point. The restart is now split into small helpers (select, cleanup, start-or-stop) to stay within the complexity limit.

Validation

Native, on this host. This used the 5.0.0-next.11 hack-native with this branch's CLI and a synthetic single-service project. Each case started in a fresh private native home and was run the way the report describes: hack restart as a background job (SIGINT inherited as ignored) and one SIGINT.

SIGINT point Restart exit Output Afterwards
before stop (0.3 s, preflight) 130 after 1 s canceled before cleanup; nothing stopped; the original up keeps running ps absent after that up is stopped; hack up ready
during stop (2 s, the reported case) 130 after 10 s Stopping… / stopped; owned cleanup confirmed no publication, no process; hack up ready
during start, before hand-off (Preparing native project input) 130 after 1 s Stopping… / owned cleanup confirmed hack up ready
during start, after hand-off (Starting services) 130 after 1 s Stopping… / Native project stopped. hack up ready (no pending restart)

On 5.0.0-next.11 the same 2 s SIGINT let the restart run to ready, and it stopped only on a second SIGINT.

Tests.

  • Unit tests in tests/native-project-restart.test.ts cover: a stop request in preflight, in cleanup, before hand-off, after hand-off and before ready, and after hand-off; SIGTERM → 143; a failed start keeps its intent.
  • A real-process test sends SIGINT to a worker (tests/fixtures/native-restart-signal-worker.ts). The worker starts under /bin/sh with trap "" INT (inherited ignore), in its own process group, with stdin closed. The test holds the worker in each of five phases and checks the exit code 130, the output lines, a single "stopping" line and no SIGINT listener left at exit.
  • Further regression tests (for review feedback): a cancellation during the runtime-up wait is confirmed only after cleanup completes; a failing cleanup is not reported as a confirmed cancellation; after the hand-off, a confirmed cancellation retires the intent, while an uncertain one keeps it.
  • Focused suites (native-project-restart, native-project-start, project-restart-command, native-https-startup-failure, native-project-https): 230 pass, 0 fail. Every test file that touches up or restart (106 files): 2232 pass, 96 fail. All 96 failures also fail on a clean origin/next worktree.
  • bun run --cwd packages/cli typecheck: only the pre-existing tests/fixtures/atomic-write-foreign-owner-worker.ts(39,25) error, which also fails on origin/next.
  • Biome on changed files: clean, apart from the pre-existing complexity warning in native-project-start.ts.
  • Full suite through the bounded runner: two bounded runs. The first covered 409 of 660 files before the 2400 s watchdog fired: 5657 pass, 43 fail. The second covered the remaining 251 files to completion: 3747 pass, 44 fail. Every failing test also fails on a clean origin/next worktree in this environment. I reran each failing file there. They are in native-compose-* / native-vm-file-*, git-info-exclude, pre-commit-hook, runtime-turbo-inputs, test-config-compiler, native-ca-doctor-cli and bun-version-guard.

Gaps

  • Cancel after hand-off but before the new run's mapping is published prints Native project stopped. without the retention sentence. That is the existing up behavior at the same point, and it is left unchanged.
  • During cleanup, a second Ctrl-C is absorbed until the owned stop finishes. Unlike a ready up, it does not force a running down hook.
  • No Rust change. Not yet checked with an installed candidate.

Refs HACK-1953

🤖 Generated with Claude Code

A native `hack restart` installed no SIGINT or SIGTERM handler until its
new foreground start did. A first Ctrl-C during preflight, the cleanup
of the old run or the early start was therefore either dropped (a
SIGINT inherited as ignored, as for a background job, which is how the
restart reached ready and needed a second Ctrl-C) or ended the frontend
mid-restart with its restart lock and intent left behind.

The restart now handles stop requests from its first step until the
new start installs its own handlers, then hands them off:
- during preflight it ends with nothing stopped;
- during cleanup the owned stop runs to completion, the old frontend's
  finalizers are awaited and the new run is not started;
- before the hand-off the start is abandoned before any hook or
  service;
- after the hand-off the start stops its run as an interrupted `up`.

Every cancel prints the same lines as Ctrl-C after `up` (one
"Stopping..." line, then the confirmed-stop line), removes the restart
intent so a plain `hack up` resumes the retained data, and exits 130
(143 for SIGTERM).

Refs HACK-1953

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Nov 7, 2026

Copy link
Copy Markdown

HACK-1953

@roodboi

roodboi commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-07T20:15:40.235636Z b4c9710 Manual request
🔒 Security Review ✅ Completed 2026-11-07T20:16:16.944390Z b4c9710 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 378b05f9f9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/backends/native-project-restart.ts Resolved
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 378b05f9f9

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

… early

After the stop-signal hand-off, a first Ctrl-C while the new start was
still discovering host dependencies or waiting for `runtime up` made
`startNativeProject` throw rather than return an interrupted exit, so
the restart rethrew and left its pending intent behind although no new
graph was admitted; the next plain `hack up` refused.

`startNativeProject` now reports a confirmed cancellation: a stop
request before any service was requested, with no HTTPS acquisition
failure, ends in `NativeStartupCanceledError` with `beforeServices` set
once its own cleanup (hooks, finalization) completed. A cleanup failure
still replaces it. The restart retires the intent on that outcome and
exits with the start's interrupted code; any other failure keeps the
pending restart.

Refs HACK-1953

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: b4c9710821

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: b4c9710821

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant