Skip to content

fix(daemon): close startup and shutdown ownership gaps - #3795

Merged
thymikee merged 4 commits into
mainfrom
t3/daemon-entry-path-check
Oct 18, 2026
Merged

thymikee merged 4 commits into
mainfrom
t3/daemon-entry-path-check

Conversation

@thymikee

@thymikee thymikee commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Summary

Follow up on #3781 across 12 files, including daemon lifecycle and an upstream help-assertion repair.

  • Reject missing pinned entries and share one entry-file declaration.
  • Roll back every acquired listener after partial startup failure.
  • Resolve relative auth hooks from captured host cwd.
  • Bound platform-owner shutdown and retain registration, lock configuration, and runtime state for unfinished work.
  • Load startup helpers during initialization; keep the eager-import budget unchanged.

Validation

  • Head: 5d3e7f1fad65546d99a8b18e32e510a96ffdd17d, rebased onto feca8cb2fc; daemon patches preserved by range-diff.
  • pnpm check:affected --run --head 5d3e7f1fad: 36 checks passed (exactCommit=true, checkoutChanged=false).
  • Original regressions, c966724bea → 50ae4e57ab: runtime 4 failures → 10 passes; entry checks 2 failures → 11 passes.
  • Eager gate: 3 failures at 50ae4e57ab, then 849 passes. Upstream foldable-help assertion: 1 failure before repair, then 22 passes. Combined focused suite: 871 passes.
  • Live iOS 26.2 at 50ae4e57ab: Settings open, native snapshot (18 nodes), close, graceful daemon stop --clean; runner cleanup completed, registration/lock removed, simulator powered off.
  • Size: +3.2 kB unpacked / +1.2 kB compressed; independent smaller-design assessment justified the scope. Reviews found no high/medium issues.

GitHub CI passed on this head, including Coverage and platform smoke checks.

View guided diff Turn on auto-fix

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-18 12:08 UTC

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.39 MB 5.40 MB +3.2 kB
Package (unpacked) 5.39 MB 5.40 MB +3.2 kB
Package (download) 1.63 MB 1.63 MB +1.2 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 34.4 ms 36.1 ms +1.7 ms
CLI --help 63.0 ms 64.4 ms +1.3 ms

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 11 files

You’re at about 99% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/daemon/src/server/http/daemon-runtime-bounded-close.test.ts">

<violation number="1" location="packages/daemon/src/server/http/daemon-runtime-bounded-close.test.ts:200">
P3: This race leaves its one-second `timers/promises` timer running after `closing` wins, adding an unnecessary one-second live handle to every passing test run and potentially delaying worker shutdown. Abort the timeout when the race settles.</violation>
</file>

<file name="packages/daemon/src/server/http/server-shutdown.ts">

<violation number="1" location="packages/daemon/src/server/http/server-shutdown.ts:201">
P1: This deadline returns while the platform-owner shutdown is still running, so the daemon can report pending and continue the rest of shutdown while the owner still mutates platform resources; retaining the registration and runtime state does not cancel or join that work. Pass a cancellation/deadline signal through the owner shutdown, or keep the close synchronized with the owner operation before proceeding with dependent cleanup.

(Based on your team's feedback about cancelling timed-out asynchronous work.)</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic


async function closeWithinBudget(
close: () => Promise<readonly PluginCleanupOutcome[]>,
/** Settles one shutdown operation at its deadline without cancelling its unfinished work. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: This deadline returns while the platform-owner shutdown is still running, so the daemon can report pending and continue the rest of shutdown while the owner still mutates platform resources; retaining the registration and runtime state does not cancel or join that work. Pass a cancellation/deadline signal through the owner shutdown, or keep the close synchronized with the owner operation before proceeding with dependent cleanup.

(Based on your team's feedback about cancelling timed-out asynchronous work.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/daemon/src/server/http/server-shutdown.ts, line 201:

<comment>This deadline returns while the platform-owner shutdown is still running, so the daemon can report pending and continue the rest of shutdown while the owner still mutates platform resources; retaining the registration and runtime state does not cancel or join that work. Pass a cancellation/deadline signal through the owner shutdown, or keep the close synchronized with the owner operation before proceeding with dependent cleanup.

(Based on your team's feedback about cancelling timed-out asynchronous work.) </comment>

<file context>
@@ -182,27 +188,30 @@ export function createPluginClose(params: {
 
-async function closeWithinBudget(
-  close: () => Promise<readonly PluginCleanupOutcome[]>,
+/** Settles one shutdown operation at its deadline without cancelling its unfinished work. */
+export async function settleWithinBudget<T>(
+  close: () => Promise<T>,
</file context>

});
const closing = runtime.close();
try {
const report = await Promise.race([closing, delay(1_000, null)]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This race leaves its one-second timers/promises timer running after closing wins, adding an unnecessary one-second live handle to every passing test run and potentially delaying worker shutdown. Abort the timeout when the race settles.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/daemon/src/server/http/daemon-runtime-bounded-close.test.ts, line 200:

<comment>This race leaves its one-second `timers/promises` timer running after `closing` wins, adding an unnecessary one-second live handle to every passing test run and potentially delaying worker shutdown. Abort the timeout when the race settles.</comment>

<file context>
@@ -163,3 +169,49 @@ test('the executable writes unfinished work to stderr after a close, and a clean
+  });
+  const closing = runtime.close();
+  try {
+    const report = await Promise.race([closing, delay(1_000, null)]);
+    expect(report).not.toBeNull();
+    expect(report!.platformOwners).toBe('pending');
</file context>
Suggested change
const report = await Promise.race([closing, delay(1_000, null)]);
const report = await (async () => {
const timeout = new AbortController();
try {
return await Promise.race([closing, delay(1_000, null, { signal: timeout.signal })]);
} finally {
timeout.abort();
}
})();

@thymikee

Copy link
Copy Markdown
Member Author

I reviewed 50ae4e5 and found no blocking problems in the code. Not blocking: platformOwnersPending in daemon-runtime.ts stays true when an owner shutdown rejects, so the finally block would keep the registration, lock and runtime state (the production gateway uses Promise.allSettled, so this cannot happen today; setting it only on 'deadline' would fix it). options.cwd ?? process.cwd() in http-server.ts keeps a second cwd source beside HostEnvironment, and only tests omit it. drainInFlightRequests (daemon-runtime.ts) could use settleWithinBudget as a follow-up. Take or leave any of these.

The Coverage job failed, and its log is cut off. No vitest summary or failing test name survives. It runs the full unit suite, which includes the three new runtime tests and the close path many daemon tests use, so I cannot say if the failure is from this PR. Main CI at 4893253 is green, but this PR is based on c966724. Please rerun the job, or run pnpm gate unit-ci at head, to find the failing test, or show that the failure is unrelated, before merge. There are no conflicts.

I did not run any tests. I judged that the new regression tests would fail on base by reading the old code. The live iOS stop --clean run is only in the PR body. The pending-owner path has no live evidence, since only a controlled runtime double can reach it. I also did not check that vi.spyOn on the transport ESM namespace catches the named import in server-startup.ts under v8 coverage; the author reports it passes locally.

On the other review threads, the lower-priority thread on the test timer in daemon-runtime-bounded-close.test.ts still applies (#3795 (comment)). The P1 thread on bounded shutdown (#3795 (comment)) does not apply, so please resolve it. The non-cancelling budget is the intended policy, the same as for requests and plugins. The owner contract has no signal to pass. The registration, lock and runtime state stay held while work is unfinished, and src/daemon.ts exits the process right after the report.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 18, 2026
@thymikee
thymikee force-pushed the t3/daemon-entry-path-check branch from 76b3086 to 5d3e7f1 Compare October 18, 2026 10:25
@thymikee

Copy link
Copy Markdown
Member Author

The code looks good at 5d3e7f1. The delta since 50ae4e5 only changes when server-startup.ts loads, and I found no code problems.

All 20 checks pass on 5d3e7f1, and the platform smoke checks start the daemon through the deferred server-startup import, so CI covers the new route. I did not run the eager-import gate or any tests myself, so the budget claim rests on CI and your counts. The live iOS run was at 50ae4e5; since the delta only changes load timing, I do not ask for a new one. There are no conflicts.

The Cubic P1 on shutdown past the deadline does not apply, and you can resolve it: the shutdown reports platformOwners as pending and keeps registration, lock and runtime state while work is unfinished, and the bounded-owner test asserts this (#3795 (comment)).

Not blocking: the Cubic timer thread on daemon-runtime-bounded-close.test.ts still applies as optional test cleanup (pass an AbortSignal so the 1s delay stops after closing wins), and the help-test assertion fix in cli-help-first-screen.test.ts is unrelated to daemon ownership, so you can drop it on rebase if main fixes it first.

@thymikee
thymikee merged commit 11de319 into main Oct 18, 2026
20 checks passed
@thymikee
thymikee deleted the t3/daemon-entry-path-check branch October 18, 2026 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant