Skip to content

refactor(apple): simplify warm runner ownership - #3357

Merged
thymikee merged 20 commits into
mainfrom
t3/3321-finish-and-pr
Oct 10, 2026
Merged

thymikee merged 20 commits into
mainfrom
t3/3321-finish-and-pr

Conversation

@thymikee

@thymikee thymikee commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Summary

Give warm Apple runners one lifecycle owner. A ready, idle iOS-family Simulator runner owns its listener watch and retention timer; successful reuse ends retention, and listener loss or expiry stops that generation.

Close retires prewarm recovery and stops unfinished, busy, or disconnected runners. Graceful daemon exit stops idle retained runners; active handoff remains supported. Shared locks track their held lifetime and reentrant work, so deferred callbacks cannot overlap another owner. Clamp idle durations to Node's timer limit.

Remove the separate retention registry, epochs, rollback, watcher attach retries, and next-open notice plumbing. 19 files; 186 fewer production TypeScript lines than the incoming PR head. Scope includes #3359 and the lock primitive its callbacks depend on.

Closes #3321. Closes #3359.

Validation

Final head 8afe9598ef; runtime checks at 06362cacdd (the last commit only wraps the ADR):

  • pnpm check:quick and pnpm check:affected --run passed: 4,839 tests across 575 files, plus layering, Fallow, build, and guidance checks.
  • pnpm check:xctest-selection and the iOS XCTest build passed. Behavioral regressions and mutations demonstrate the overflow, reentrant microtask, cold-close, cancellation, watch-identity, and actual idle-disposal boundaries.
  • Claude reviewed the design and final corrections; no remaining blockers.
  • Live iOS 26.2: Settings snapshot, same-generation close/reopen reuse, and graceful retained-runner cleanup without --clean; then 60 seconds Shutdown with no lease. Earlier cold-close/shutdown runs at 1–3s gaps also stayed off for 60s.
  • All 17 review threads answered with evidence and resolved. New-head CI pending; iOS 27 and cabled physical-device checks unavailable locally.

thymikee and others added 5 commits October 9, 2026 21:01
…lator

A runner retained after `close` owns its Simulator destination: when the
Simulator is shut down externally, the retained xcodebuild's destination
machinery silently reboots it and the device stays powered on until that
runner dies (#3321).

While a runner is warm-retained, hold one idle TCP connection to the
runner's listener as a push signal. When an established watch connection
closes during retention, read the device's boot identity once (bounded
recheck, never a sample): a boot newer than the retention window proves
Xcode rebooted the destination, so stop the retained runner — which
powers the rebooted device back off — and record the typed
`runner_destination_lost` reason the next `open` reports as a response
warning. A same-boot close re-arms on the restarted generation; a
never-established connection retries on a bounded backoff before being
called `runner_unreachable`. Healthy warm reuse is untouched: killing a
retained runner whose device was not rebooted leaves the device on
(negative control), and the idle-stop policy still stands behind it.

Mechanism evidence and per-claim measurements: PR body, issue #3321.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ined runner outright (#3321)

Live runs showed three gaps: attach retries reset the window start, so a
reboot looked older than the window; a shut-down device keeps its old
launchd_sim listed for seconds, so the boot witness alone read a crash;
and a graceful stop let the dying xcodebuild reboot the device again.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… and a verdict (#3321)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-10 11:32 UTC

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.14 MB 5.14 MB +2.9 kB
Package (unpacked) 5.14 MB 5.14 MB +2.9 kB
Package (download) 1.55 MB 1.55 MB +793 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 16.9 ms 18.2 ms +1.3 ms
CLI --help 51.2 ms 52.5 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.

All reported issues were addressed across 13 files

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

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

Comment thread packages/platform-apple/src/runner/runner-destination-watch.ts Outdated
Comment thread packages/platform-apple/src/runner/__tests__/runner-destination-watch.test.ts Outdated
Comment thread packages/platform-apple/src/runner/runner-session.ts Outdated
Comment thread packages/platform-apple/src/runner/runner-destination-watch.ts Outdated
Comment thread packages/platform-apple/src/runner/runner-session.ts Outdated
Comment thread packages/platform-apple/src/runner/runner-destination-watch.ts Outdated
Comment thread src/platform-runtime-warm-runner-notice.ts Outdated
Comment thread packages/platform-apple/src/runner/__tests__/runner-destination-watch.test.ts Outdated
Comment thread packages/platform-apple/src/runner/__tests__/runner-destination-watch.test.ts Outdated
Comment thread website/docs/docs/sessions.md Outdated
…ment generations (#3321)

Review round on #3357. Retention (idle stop, destination watch, retention fact) now
enters through one retainRunnerForReuse and a failed ensureRunnerSession restores it,
a re-attach that connects classifies the replacement generation, an unreadable device
state counts as loss, the notice read waits for an in-flight decision on an open
window, and a stale handler no longer deletes a newer watch.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 9, 2026
…ner and releases its lease (#3321)

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

@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.

All reported issues were addressed across 7 files (changes from recent commits).

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

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

Comment thread packages/platform-apple/src/runner/runner-session.ts Outdated
Comment thread packages/platform-apple/src/runner/runner-session.ts Outdated
…ouched it (#3321)

A failed start that released a retained runner could re-arm retention over a
runner a concurrent start was already using, because the restore keyed on session
identity. Retention state changes now advance a per-device epoch and a restore is
valid only at the epoch it released at. retainRunnerForReuse checks the session
before it touches any retention state.

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

@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.

All reported issues were addressed across 2 files (changes from recent commits).

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

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

Comment thread packages/platform-apple/src/runner/__tests__/runner-close-finalization.test.ts Outdated
Comment thread packages/platform-apple/src/runner/runner-session.ts Outdated
…eparately (#3321)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@thymikee

Copy link
Copy Markdown
Member Author

I found one problem to fix in fbbf157, and the rest is minor. Checks are green: 21 checks, none failing. There are no conflicts. The new observeSimulatorState in runner-host.ts repeats getSimulatorState from simulator.ts almost line for line, and a third copy lives in simulator-state.ts. Any fix to the Simulator state probe now has to land in three places, and this copy is the one the reboot guard relies on. The rule: the Apple package has one Simulator-state probe. Could you export getSimulatorState from core/simulator.ts with a timeoutMs option, bind observeSimulatorState to it with the same dynamic import the next line already uses for simulator-boot.ts, and delete the inline copy? That is the only change needed before merge.

Not blocking, and you can take or leave these: in runner-destination-watch.ts, resolveConfirmDelayMs, resolveRecheckDelayMs and resolveAttachRetryBudgetMs have identical bodies (and copy resolveRunnerIdleStopMs) and add three undocumented AGENT_DEVICE_IOS_RUNNER_DESTINATION_* env vars that only tests set, so one shared reader or injected delays would do; the re-arm branch at line 172 keeps the old port, socket and replacement flag, which is safe today because cancel always detaches first, but it would be sturdier to detach and re-attach when the port or session differs; and the warning at session-open-execution.ts says "This open started a fresh runner", which completeOpenCommand never checks, and the notice is keyed per device and never expires, so it could show on a later open from another session.

Could this be much smaller? If any loss of the retained runner's listener during retention (an established socket that closes, or a refused first attach) simply stopped the runner, the boot-identity classification, same-boot re-arm, replacement-generation retry loop, confirm/recheck/retry timers, their env vars and the observeSimulatorBootTimeMs host port could all go, roughly 250-300 production lines. By the module's own comment, killing a runner whose device was not rebooted cannot power anything on. The only cost is that a runner that crashed during retention loses warm reuse, and the next open cold-starts. The epoch restore and the open notice would stay. The 688 net lines and +6.3 kB unpacked are above the thresholds in docs/agents/pull-requests.md. This needs an explicit decision that a retained runner that crashes and is restarted by Xcode does not need to survive retention, and #3359 (prewarm admission after a retaining close) could then share the same stop path.

On the open review threads, the cubic-dev-ai thread on runnerRetentionEpochs growth does not apply: the map grows by one integer per device, so it is bounded by device count, and reclaiming entries would reopen the restore race. You can resolve it.

On evidence, the live 5/5 simulator runs appear only in the PR body and I saw no artifacts, and no live run is stated for the runner_unreachable and replacement-generation paths. I did not run the tests. I judged the regression from the pre-change code. I also did not trace whether every open route reaches completeOpenCommand, and a failed open leaves the notice unconsumed.

… that replaces rather than amends (#3321)

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

@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.

All reported issues were addressed across 5 files (changes from recent commits).

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

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

Comment thread packages/platform-apple/src/core/simulator.ts Outdated
Comment thread packages/platform-apple/src/runner/runner-destination-watch.ts Outdated
Comment thread packages/platform-apple/src/core/simulator.ts Outdated
Comment thread packages/platform-apple/src/runner/runner-destination-watch.ts Outdated
@thymikee

Copy link
Copy Markdown
Member Author

Thanks for the review. State at dd52602c3: pnpm check:affected --run exits 0.

Required change: done. getSimulatorState in core/simulator.ts is exported with { signal?, timeoutMs? }, and observeSimulatorState binds to it through a dynamic import (the inline copy in runner-host.ts is deleted; the binding keeps its catch so a failed probe reads as null, because the watcher must not throw). I did not collapse simulator-state.ts: its simctl arguments come from scopeSimctlArgsForDevice, which is the same simulator-set scoping buildSimctlArgsForDevice applies, but it executes through the injected AppleToolHost (readiness and shutdown runtimes), a different execution seam from the global runXcrun. snapshot-target.ts runs the same list devices -j to read the device's runtime key, not its state, so it is a different question.

Non-blocking, all taken:

  • The three env readers are one readDurationEnvMs, and the module comment documents the three AGENT_DEVICE_IOS_RUNNER_DESTINATION_* variables as test-only shorteners.
  • attachRunnerDestinationWatch now replaces an existing watch instead of amending it in place. I did not rely on "cancel always detaches first": that detach is an asynchronous import, so the invariant was not provable at the call site.
  • The warning no longer says "This open started a fresh runner"; it reads "left by a previous close was stopped at ", which stays true whenever it is read. completeOpenCommand is reached from both open paths in session-open.ts and session-open-execution.ts; I did not enumerate routes that bypass it, and a bypassing route would leave the notice for a later open.
  • The runnerRetentionEpochs thread was already resolved.

Scope question, which needs a maintainer decision: keep the classification, or simplify to "any lost listener stops the runner"?

What simplifying removes: boot-identity classification, same-boot re-arm, replacement-generation classification, confirm and recheck delays, and the observeSimulatorBootTimeMs port, roughly 250 to 300 production lines. The only behaviour lost on the happy path is that a runner that crashes during retention and that Xcode restarts loses warm reuse.

Why I recommend keeping it, from the live runs:

  1. A refused attach is not a loss signal. Right after close the listener refused my first 3 attaches for about 3 s in several healthy runs, then accepted. "Refused first attach stops the runner" would kill healthy warm runners. A retry budget has to stay.
  2. An established close alone cannot be classified without a device read. Xcode reboots a shut-down simulator within 1 to 2 s, so by the time a state read happens the device is often Booted again, and only the boot identity says it is a new boot. A state-only check would re-arm onto the rebooted device.
  3. Without the classification at connect, a shutdown during the first seconds after close lets Xcode reboot the device and restart the runner onto the same port. That connection never closes, so nothing would stop it.
  4. Each piece maps to a live failure I hit and fixed on this branch: window start reset on retry, old launchd_sim still listed after shutdown, and the graceful stop letting the dying xcodebuild reboot the device.

If you prefer the smaller version: stop on any established close, keep the retry budget, drop the boot port and re-arm, and use one runner_lost reason. It would reopen points 2 and 3. #3359 (prewarm starting a runner after a retaining close) would not get simpler, because it is an admission problem and not a stop-path problem; both would share only invalidateRunnerSession.

Evidence. The live runs are author-run, and I did not keep artifacts from the earlier ones. Here is a fresh run at dd52602c3 (iPhone 17, iOS 27.0, Xcode 27.1 beta; device state polled every 0.5 s after simctl shutdown):

head dd52602c3, iPhone 17 iOS 27.0, Xcode 27.1 beta
## cycle 1 (close -> shutdown gap 6s)
Opened: com.apple.Preferences
Page: com.apple.Preferences
Closed: live3321
simctl shutdown at 03:00:59Z
+0s (Shutdown) xcodebuild=0
## cycle 2 (close -> shutdown gap 10s)
Opened: com.apple.Preferences
Warning: The warm iOS runner left by a previous close was stopped at 2026-10-10T03:01:03.030Z: the simulator was shut down externally while it was retained, so the runner was stopped before it could boot the simulator again. reason=runner_destination_lost
Page: com.apple.Preferences
Closed: live3321
simctl shutdown at 03:02:34Z
+1s (Shutdown) xcodebuild=0
## cycle 3 (close -> shutdown gap 3s)
Opened: com.apple.Preferences
Warning: The warm iOS runner left by a previous close was stopped at 2026-10-10T03:02:37.787Z: the simulator was shut down externally while it was retained, so the runner was stopped before it could boot the simulator again. reason=runner_destination_lost
Page: com.apple.Preferences
Closed: live3321
simctl shutdown at 03:03:58Z
+1s  xcodebuild=0
+2s (Booted) xcodebuild=0
+11s (Booted) xcodebuild=1
+27s (Booted) xcodebuild=0
+28s (Booted) xcodebuild=1
## final open (reports the notice from cycle 3)
Opened: com.apple.Preferences

Cycles 1 and 2 (6 s and 10 s gaps) end Shutdown with the runner gone within 1 s, and cycle 2's open reports the notice from cycle 1. Cycle 3 (3 s gap) is the #3359 residual: the device ends Booted with a daemon-started runner. That makes the window wider than the "under 1 s" I first reported, and I updated #3359. Not covered by a live run: runner_unreachable after the final design, and the classification at connect after refusals; both are covered by unit tests (runner-destination-watch.test.ts) and by the earlier live runs that hit them before the last two fixes.

thymikee and others added 2 commits October 10, 2026 05:06
…cept, and why two probe seams exist (#3321)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…eachable, not as a shutdown (#3321)

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

@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.

All reported issues were addressed across 3 files (changes from recent commits).

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

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

Comment thread packages/platform-apple/src/runner/runner-destination-watch.ts Outdated
@thymikee

Copy link
Copy Markdown
Member Author

Follow-up to the probe thread: a state listing that times out or is unreadable (null) used to stop the runner as runner_destination_lost, which would tell the next open the simulator was shut down externally when nothing observed that. In e2fb955 it stops the runner as runner_unreachable instead, so the stop is the same and the claim is only what is known. A listed state other than Booted is still runner_destination_lost. Pinned by 'an unreadable device state stops the runner without claiming a shutdown'. The probe budget was already raised to 5 s in 97f47a2; before that it was 2 s, as it had been since fbbf157.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@thymikee

Copy link
Copy Markdown
Member Author

Commit note for whoever reads history or squashes: 97f47a283 is titled docs(ios) but also moves SIMULATOR_STATE_PROBE_TIMEOUT_MS in runner-host.ts from 2 s to 5 s, a timing change on the reboot-guard path (a stop can wait up to 3 s longer on an unanswered state probe). I am not rewriting pushed history. If this squashes, the squash title should be the fix(ios) one. Last edit on this PR is 791bdd62f; I am not pushing anything else unless a reviewer asks.

…nd cause text (#3321)

runner_unreachable meant both 'the runner stopped answering' and 'our own state
probe could not be read'. The probe verdict is now runner_destination_unverified,
and the open warning's cause text is a total map over the reasons.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@thymikee

Copy link
Copy Markdown
Member Author

In 9c71e66 runner_unreachable means only that the runner stopped answering. A state probe that times out or is unreadable now stops the runner as its own typed reason, runner_destination_unverified. The open warning's cause text is a total map over the three reasons, so it says its connection closed and the simulator's state could not be read, not that the runner stopped answering. cli-help.ts and sessions.md name all three. The Markdown-bullet typo was already fixed in 791bdd6. This is the last edit unless a reviewer asks for another.

@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.

All reported issues were addressed across 6 files (changes from recent commits).

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

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

Comment thread packages/platform-apple/src/runner/runner-destination-watch.ts Outdated
…3321)

Comment-only; no behaviour change.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… classifying the loss (#3321)

Drops the boot-identity classification, same-boot re-arm, replacement-generation retry,
confirm/recheck timers, their env vars, the two host ports and the extra probe export. The watcher
now stops the retained runner when its idle connection closes, or when the port stays refused past
a bounded retry budget; the next open reports reason=runner_connection_lost.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@thymikee

thymikee commented Oct 10, 2026 •

Copy link
Copy Markdown
Member Author

Decision taken: the simple design, in fe6a625d9.

Removed: the boot-identity classification, same-boot re-arm, replacement-generation retry, the confirm and recheck timers with their three env vars, the observeSimulatorBootTimeMs and observeSimulatorState host ports, and the getSimulatorState export (simulator.ts, runner-host.ts and host.ts are back to main).

Behaviour now: a closed established connection, or a port still refused after 15 one-second retries, stops the runner. The notice is a single reason=runner_connection_lost, and takeRunnerWarmLossNotice is synchronous again. The retry stays because healthy runners refuse for about 3 s right after close.

Size: the production diff is about 320 added lines (was about 690); the whole PR is 692 added lines including tests (was 1,342).

Live at this head: 8 s and 12 s gaps between close and simctl shutdown both end Shutdown with the runner gone, and the next open carries the warning. pnpm check:affected --run exits 0.

Accepted costs: a runner that crashes during retention and is restarted by Xcode also loses warm reuse, and a shutdown during the first seconds after close stays the #3359 window.

@thymikee thymikee changed the title fix(ios): stop a warm runner from silently rebooting a shut-down simulator refactor(apple): simplify warm runner ownership Oct 10, 2026

@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.

All reported issues were addressed across 29 files (changes from recent commits).

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

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

Comment thread docs/adr/0005-ios-runner-interaction-lifecycle.md Outdated
Comment thread packages/platform-apple/src/runner/runner-lifecycle.ts
Comment thread website/docs/docs/sessions.md Outdated
Comment thread website/docs/docs/sessions.md Outdated
Comment thread packages/platform-apple/src/runner/runner-retention.ts Outdated
Comment thread website/docs/docs/sessions.md Outdated
Comment thread packages/platform-apple/src/runner/runner-adoption.ts Outdated
Comment thread docs/adr/0005-ios-runner-interaction-lifecycle.md Outdated
Comment thread packages/platform-apple/src/runner/runner-session-types.ts Outdated

@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.

All reported issues were addressed across 11 files (changes from recent commits).

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

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

Comment thread docs/adr/0005-ios-runner-interaction-lifecycle.md Outdated
@thymikee

Copy link
Copy Markdown
Member Author

Thanks for the update. At 8afe959 most of the earlier review is fixed: the simulator export is back to main, the destination watch files are gone, the docs now scope cleanup to graceful daemon shutdown, and the keyed-lock and retention-timer fixes are in. CI is green, with 22 checks and none failing on 8afe959, and there are no conflicts. I did not run the tests, and the one open item is live evidence.

The new route is a watch opened after the first exchange, and its socket close triggers stopRetainedRunner('listener_lost') (runner-retention.ts#L51). The stated runs cover a different watch design (fe6a625), the graceful daemon-shutdown route, or undated earlier cold-close runs, so no live run shows this route. #3321 and #3359 are device-timing bugs, and loopback fixtures cannot show that the stop wins the race against xcodebuild's re-boot after a real simctl shutdown. Could you run this on a Simulator at 06362ca or later? Run open com.apple.Preferences --relaunch, snapshot -i, close, then xcrun simctl shutdown <udid> at (a) 1-3 s and (b) 8-12 s after close, 3 runs each. Please report that the device stays Shutdown for 60 s, pgrep -fl xcodebuild shows no runner for the udid, and no runner lease file remains. The daemon log should show ios_runner_warm_stop reason=listener_lost for (b), and ios_runner_retain_skipped or the prewarm retirement for (a). Please also report one close and reopen with no shutdown, left idle past 60 s, where there is no ios_runner_warm_stop, so we know the watch did not drop a healthy retained runner.

Once that run is posted, this is ready to merge from my side. The cubic-dev-ai threads are all fixed at this head or resolved as benign, so the author can resolve them: #3357 (comment), #3357 (comment), #3357 (comment), #3357 (comment), #3357 (comment), #3357 (comment), #3357 (comment), #3357 (comment) (a late canceled health result must not invalidate a retained generation, and the canceled test arm pins it), #3357 (comment), #3357 (comment), #3357 (comment), #3357 (comment) (a refused watch only makes the generation ineligible for retention, so it fails closed), #3357 (comment) (the cold arm pins that close does not wait on the build's session lock), #3357 (comment), #3357 (comment) (uptime is read-only and goes through the waitForRunner mock), #3357 (comment) (a cold build has no ready candidate, so it skips the lock), #3357 (comment), #3357 (comment), #3357 (comment) (the public close policy is simulator-only, so the simulator-scoped docs are accurate), #3357 (comment), #3357 (comment), #3357 (comment), #3357 (comment).

@thymikee
thymikee merged commit c6bdc06 into main Oct 10, 2026
22 checks passed
@thymikee
thymikee deleted the t3/3321-finish-and-pr branch October 10, 2026 11:31
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

1 participant