Skip to content

fix(ios): serialize status bar normalization around simulator captures - #4136

Merged
thymikee merged 15 commits into
mainfrom
fix/sm11-status-bar-restore
Nov 2, 2026
Merged

thymikee merged 15 commits into
mainfrom
fix/sm11-status-bar-restore

Conversation

@thymikee

@thymikee thymikee commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Summary

Fix SM-11: hold snapshot → normalize → capture → restore under one iOS simulator status-bar lock. Plain captures and Wi-Fi/airplane writes share the hold; writes retain their budget and invalidate the cleared-state cache before mutation.

The lock and cache use platform:kind:udid. CoreSimulator creates a UUID per device; copied sets with colliding UDIDs may over-serialize, an accepted trade-off. Set-path identity changes remain reverted to main. Twelve files remain in the PR.

Help/docs distinguish the request timeout (90 seconds for standalone CLI settings, including waiting) from the 10-second status-bar command limit. Screenshot reads/preparation allow 10 seconds per command; restoration allows 30 seconds per command.

Validation

Head be0846f81c868339e01a1faff6047ffe1450acee; this round changes only help/docs from f01442f6.

  • Five focused suites: 69 passed. No behavior changed or new tests needed.
  • Exact-head affected gate passed: 16,762 unit and 317 related tests; one skipped.
  • Production route, one session, at f01442f6: worktree build on local / e2e-apex; normalized screenshot restores the seed, then concurrently requested Wi-Fi off executes and survives.
  • Two-capture overlap harness, at f01442f6: two isolated embedded HTTP daemon runtimes with seeded sessions share the platform owner; both restore the seed.

The daemon's per-device claim prevents two sessions on one device, so overlap is reachable only through direct platform callers and the harness. The capture route is unchanged; live evidence was not rerun. See live evidence and this review round. Coverage/integration remain GitHub-owned.

The screenshot status bar transaction read the simulator's current overrides
as the state to restore to, applied its deterministic normalization, captured,
and restored. Two captures that overlapped on one simulator broke that: the
second read the first capture's normalization as its "previous" state, so its
restore leg re-applied the normalized network and Wi-Fi overrides and left them
on. The daemon serializes ordinary requests for a device, but the capture
interface itself did not, so any caller outside that path hit it.

`withNormalizedSimulatorStatusBar` now owns the whole normalize -> capture ->
restore window and holds the simulator's keyed lock across it, so a capture can
only ever snapshot the caller's own overrides, and a capture that throws still
restores before the queued one starts.
@vercel

vercel Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
agent-device-website Ready Ready Preview Nov 1, 2026 10:29pm UTC

Request Review

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.67 MB 5.67 MB -505 B
Package (unpacked) 5.67 MB 5.67 MB -505 B
Package (download) 1.72 MB 1.72 MB -319 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 35.6 ms 32.4 ms -3.2 ms
CLI --help 70.5 ms 68.6 ms -2.0 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 6 files

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

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

@apex-sentinel-staging

apex-sentinel-staging Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Apex Sentinel

Verdict: approve

Since the last review: 5 new · 0 still open · 6 no longer raised

5 finding(s): 3 minor, 2 nit. Verdict: approve. reached max rounds (1).

🟡 Minor (3)

Where Finding
packages/platform-apple/src/core/screenshot-status-bar.ts:96 🆕 Cancelled normalized captures now hold the simulator's plain captures for up to ~70s after the caller gave up
packages/platform-apple/src/core/screenshot-status-bar.ts:209 🆕 Lock/cache key drops simulatorSetPath, so a colliding UDID in another device set silently loses its caller status-bar overrides
packages/platform-apple/src/core/screenshot.ts:272 🆕 Runner simulator capture now performs container lookup and file copy inside the status-bar window
🔵 Nit (2)
Where Finding
packages/command-surface/src/capture/settings.ts:106 🆕 Status-bar timeout budgets are spelled out in three places
packages/platform-apple/src/core/screenshot-status-bar.ts:130 🆕 Signal-dropping guard is applied on only one of the two re-entry paths

Reply under an inline comment with @apex-sentinel-staging note ... or @apex-sentinel-staging pivot ... to re-check that finding.
Comment @apex-sentinel-staging review on this conversation to run a full review.

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 2 new findings on e6a411d. The verdict and the full list are in the summary comment.

Route a capture without --normalize-status-bar through the same per-simulator
lock so it cannot take its screenshot of the temporary normalized bar another
capture installed. Pass the request's abort signal to the lock and bound every
simctl status_bar call so a wedged tool cannot hold a simulator's captures.
Restore stays uncancellable: the normalization is shared device state.

Release both capture gates in a finally, pin the propagated capture error,
throw on an override value the fake cannot list, and rename the cleared-status
bar case to what it asserts.

@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

An iOS simulator's runner capture renders the same screen a simctl capture
does, but the interactor's runner backend called it directly, so it could copy
an image of the temporary normalized overrides another capture had installed.
It now takes the same per-simulator reservation without normalizing the bar,
and re-enters the one the simctl fallback already holds.

Take both capture reads at the top of the capture step, where the status bar
owner still holds the state the PNG is taken with, and settle both captures
through one helper so a rejection is never left unobserved.

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 4 new findings on b5fa69d. The verdict and the full list are in the summary comment.

One request's two capture legs both run inside the status bar window, and the
runner leg takes that same window. Drive the real normalization around the real
runner fallback with only the tools faked, and assert the order: snapshot,
normalize, runner capture, restore. The runner leg re-entering that window is
what keeps the nested reservation from opening a second one.

Give the restore a longer deadline than setup: leaving the normalization on the
simulator is the failure this owner exists to prevent, and an untimed wedged
tool would hold the simulator forever. State in the doc which commands the
request's abort signal does and does not reach.

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 2 new findings on 6a8579c. The verdict and the full list are in the summary comment.

A runner capture nested in the status bar window its own request held was
cancelling on that request's abort signal, so a normalized capture that fell
back to the runner after an abort never took the image while the window still
restored a bar it had normalized for nothing. The reservation now drops the
signal once the caller already holds the simulator's window, and the restore
comment states that its budget applies per command.

@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

@thymikee
thymikee force-pushed the fix/sm11-status-bar-restore branch from 06caa38 to e84c775 Compare November 1, 2026 12:23

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 6 new findings on e84c775. The verdict and the full list are in the summary comment.

A `settings wifi` or `settings airplane` write that landed while a normalized
capture held the simulator's status bar was reverted by that capture's restore,
which wrote back the bar it had snapshotted. Both writers now take the same
per-simulator hold, so a setting lands after the restore instead of under it. A
restore whose clear command fails now drops the known-cleared answer, because
the simulator holds whatever the failed command left, and the next capture has
to read the bar again. The runner paths read one named predicate each, and the
kernel pins that heldKeyedLockKeys reports exactly the keys withKeyedLock would
re-enter.

@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/kernel/src/keyed-lock.test.ts Resolved

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 5 new findings on 61a4a6a. The verdict and the full list are in the summary comment.

A restore whose clear command fails leaves the deterministic bar on the
simulator. Reading the bar again would make the next capture adopt that
normalization as the caller's state and hand it back on every later capture, so
the answer that says the bar is cleared now survives the failure: the next
capture skips the read and its own restore clears the leftover. The kernel test
checks the pending hold before it is awaited, and the runner branch comment
names the targets that take a status bar window.

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 3 new findings on b02d672. The verdict and the full list are in the summary comment.

`simctl status_bar` runs scoped to a device set, so one udid in two sets is two
bars. The transaction and cache key now carry the resolved set path, so records
of one simulator still share one window while simulators of different sets keep
their own. The settings writes take the request's signal, so a write queued
behind a capture window can still be cancelled, and the screenshot help states
that an iOS simulator capture waits for the bar and reports cancellation.

@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

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 8 new findings on af11ed6. The verdict and the full list are in the summary comment.

The wifi and airplane writes now hold the per-simulator window every iOS
capture queues behind, so a wedged `simctl status_bar` inside that hold would
stop those captures. Each of the three commands keeps the same per-command
budget the capture's own status bar commands use, asserted by a new case in the
concurrency file. The settings cancellation case releases its capture gate
before awaiting the write, so a regression reports its assertion instead of
hanging, and the screenshot help says cancellation covers the wait for the bar.

@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 4 files (changes from recent commits).

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

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

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 4 new findings on 97a1090. The verdict and the full list are in the summary comment.

@thymikee

thymikee commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

Live device evidence at head 6d59c2b3e5

Device: simulator e2e-apex, udid 6832D598-6CC2-49DF-9623-64CBF79ACB35, iOS 26.2 (com.apple.CoreSimulator.SimRuntime.iOS-26-2), host local.

Captures are taken by this worktree's capture owner — captureSimulatorScreenshotWithFallback from packages/platform-apple, the same entry the daemon's screenshot command reaches — invoked from /private/tmp/sm11-live3/gated-two.mjs, gated.mjs, settings-order.mjs and settings-cancel.mjs (node <script> <repoRoot> <udid>). simctl status_bar override / list / clear were used only to seed the caller's overrides, read the bar back, and clear it at the end. Each capture is held open inside its own capture step until the simulator really shows the temporary bar, so the second capture's snapshot is guaranteed to overlap it — no timing guesswork.

Caller seed: --dataNetwork lte --wifiMode active --wifiBars 2 --cellularMode active --cellularBars 4 --operatorName 'SM11 Carrier'.

Check base f7879cf2d2 head 6d59c2b3e5
Two overlapping normalized captures (3 runs) leaked 3/3, bar left WiFi Mode: 3, WiFi Bars: 3, once DataNetworkType: 11 VERDICT: caller overrides restored every run
Plain capture started mid-window (no --normalize-status-bar) captured the temporary bar, Time: 09:41 saw the caller's bar: WiFi Mode: 3, WiFi Bars: 2
settings wifi on started mid-window applied after 675 ms and was reverted — bar ended WiFi Mode: 2, WiFi Bars: 0 waited for the window (3.0–4.4 s) and survived — bar ended WiFi Mode: 3, capture's other overrides restored
settings wifi on cancelled mid-window n/a (no hold) rejected after 403 ms, VERDICT: cancelled write wrote nothing, caller bar intact
End state — simctl status_bar … clear, then list → Current Status Bar Overrides: empty

@thymikee thymikee left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Independent review: needs-changes.

Reviewed exactly merge-base f7879cf2d266ee1dd476d0342b2819bdeceb647b → head 6d59c2b3e5680fd7ea84ce09891b610c3fd1a923. Source remained read-only.

  1. P2 — Canonicalize the simulator set before deriving its lock/cache key. packages/platform-apple/src/core/screenshot-status-bar.ts:213-215 includes the raw set-path spelling in the key. simulatorAddressFor only trims that path (packages/kernel/src/device-isolation.ts:7-11), and inventory preserves the spelling (packages/platform-apple/src/simulator-inventory.ts:79-91). With no operator-pinned set, two requests may therefore select the same UDID through /path/to/set and /path/to/set/.. Both simctl --set commands reach the same directory and simulator, but the keys differ. Capture B can enter while A's deterministic overrides are installed, snapshot them as caller state, and restore them after A restores the real caller state: the original leak remains reachable. Omitting the set versus explicitly naming the default set is another instance. Resolve one canonical simulator-set identity, including the implicit default, at the address boundary and use it consistently for both the transaction and cleared-state cache. Add a same-directory/different-spelling overlap case alongside the existing genuinely-different-sets case.

  2. P2 — Invalidate the cleared-state cache before a settings override can mutate the bar. packages/platform-apple/src/core/app-settings.ts:115-120 (also airplane-on at 134-155) invalidates only after the newly timed command succeeds. Concrete case: a prior normalized capture has recorded this simulator as known-cleared; settings wifi off applies --wifiMode failed but simctl then stalls and hits the new 10-second deadline. The lock releases, but the cache still says cleared. The next normalized capture skips status_bar list, overwrites the setting, and restores with clear, permanently discarding the applied caller override. A timeout does not prove that the write had no effect. Invalidate inside the hold before attempting either override, so failed or timed-out writes force the next capture to read the actual state. Cover a tool double that applies the override and then throws the timeout; merely asserting that timeoutMs was supplied does not cover this failure.

  3. P3 — Document the settings wait and timeout on the owning user surfaces. packages/platform-apple/src/core/app-settings.ts:112-123 / 130-164 changes settings wifi and settings airplane from an immediate write to a queued operation with a 10-second execution budget. A caller issuing a setting during a long screenshot now waits for capture and restoration, or receives cancellation before any write. Only screenshot CLI detail was updated (packages/command-surface/src/capture/screenshot.ts:77); settings --help (packages/command-surface/src/capture/settings.ts:97-108) and the settings/screenshot sections of website/docs/docs/commands.md do not explain this behavior. Add the wait, cancellation, and per-command timeout semantics to settings help and the user docs. Clarify that 10 seconds bounds execution after acquisition, not time spent queued. This is required by AGENTS.md's help-and-user-docs rule.

Requested adversarial/design checks:

  • For identical address strings, the lock covers snapshot → normalization → capture/fallback → restoration; finally releases it after capture failure. Distinct actual device sets with the same UDID have distinct keys. Physical-device and macOS runner routes bypass this lock.
  • The status-bar tool calls have 10-second setup/write and 30-second restore budgets. The simctl capture has its existing 20-second retry deadline; boot recovery and runner startup/transport also have their own budgets. Thus the ordinary hung-tool paths are bounded, but this is not one overall transaction or queue deadline. An uncancelled waiter waits through all preceding holders and does not receive a holder's failure; a queued caller with a signal rejects with its own abort reason. File/PNG processing is also inside the hold and has no overall transaction watchdog, so the per-command timeouts are not proof of a hard bound on every possible stall.
  • The existing AsyncLocalStorage re-entrant lock avoids a runner fallback or nested settings write waiting on its own hold. A settings write deliberately made inside a normalized capture callback would still be overwritten by that callback's enclosing restore; I found no production capture leg that performs such a settings write, so I am not treating that hypothetical call graph as a regression.
  • Including plain and runner captures is necessary to keep them from observing temporary normalization. Including wifi/airplane writes prevents restoration from undoing concurrent writes; bounding those commands is necessary once they can block screenshots. Keep these changes together; no new shared helper package or unrelated command-budget framework is needed. The diff adds 185 net production lines and 976 net test lines; the posted Size report is +982 B unpacked, below the mandatory size-review thresholds.

Thread 4435764303: follow-up, not an overlap-fix blocker. The failed-restore-clear path with real caller overrides is real: it can leave normalization behind and a later capture can adopt it. The base has the same clear → override restore sequence and swallowed restore error. Recovering the actual caller values requires retaining the original snapshot for recovery; pretending the bar was cleared only prevents adopting normalization while discarding those values. Track that separately with a caller-overrides regression case. The new explanatory comment at screenshot-status-bar.ts:177-181 should explicitly limit its recovery explanation to the already-known-cleared case.

Validation limits: static source/caller/test review and git diff --check only; no unit tests, device operations, or CI waiting. The new stateful concurrency tests exercise the real capture/status-bar boundary and visibly distinguish the base overlap behavior. The PR body reports identical-test base 8 failed / 2 passed versus head 10 passed, plus live base/head evidence; I did not independently reproduce those counts. Source approval is blocked by findings 1 and 2, independently of CI.

@thymikee

thymikee commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

The change at 6d59c2b looks right to me, apart from the open invalidation-order thread in the first note below, which should be fixed before merge. CI is green (18 checks, none failing at this commit), and there are no conflicts.

Not blocking, and fine to take or leave: (1) wifi and airplane on in app-settings.ts drop the known-cleared answer only after the write returns, so a write that lands and then hits the new 10 s timeout leaves a stale answer. The rule is that any status bar write that may land must invalidate the cache before it starts. Please call the invalidation before the write in both override branches, or export one held write helper from screenshot-status-bar.ts that does it. (2) The exclusive lock in screenshot-status-bar.ts also serializes plain captures of the same simulator. That is acceptable and the cliDetail sentence covers it, so a shared mode is not needed here. (3) The comments in screenshot.ts and the long blocks in screenshot-status-bar.ts say the reservation covers density normalization, but the runner path does not do that. One sentence per export that states the rule is enough.

Evidence: the live run called the platform-apple owner directly, not through the daemon, so the request signal path was not run live. Optionally, run two overlapping screenshot --normalize-status-bar calls on one simulator from two sessions, then check xcrun simctl status_bar <udid> list for the seeded caller overrides. I also did not run the new concurrency tests on base, so the "8 failed | 2 passed" claim is unchecked. I read the test bodies and they assert the final bar state through the production flow.

On the open threads: the cubic thread on the invalidation order (#4136 (comment)) still stands and is the same as the first note. Four lower-priority threads from the other review still apply and are docs or naming nits. Two do not apply and can be resolved: the failed-clear recovery is the intended path (#4136 (comment)), and airplane off already sent a plain clear on base (#4136 (comment)).

Nothing stops the merge. I suggest taking the invalidation reorder first, then replying on the open threads.

@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 website/docs/docs/commands.md Resolved
…aliases

Resolve the simulator device set to one canonical path where the address is minted, and
use that value for both the status bar lock key and the `--set` argument, so aliased or
non-normalized spellings of one set cannot open two windows over one status bar. Naming
the default device root now answers as the default set. Platform code reaches the
canonical path through the host-kit file port, which is where host authority belongs.

Drop the cached known-cleared answer before a `wifi` or `airplane` status bar command
instead of after it: a write that hits its deadline may still have reached `simctl`, so
no cached answer survives the attempt and the next capture reads the bar.

Document that `settings wifi` and `settings airplane` wait for the simulator's screenshot
window and bound their own write to 10 seconds, in `settings --help` and the command
reference.
@thymikee
thymikee force-pushed the fix/sm11-status-bar-restore branch from b999afd to 6f7b9ea Compare November 1, 2026 14:47

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 5 new findings on 6f7b9ea. The verdict and the full list are in the summary comment.

@thymikee

thymikee commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

All three findings from #4136 (review) are addressed at head 6f7b9ead18. Each fix is test-first: the test was written first, seen to fail at the previous head with its own message, and mutation-checked so it fails again when only that fix is removed.

P2 — set-path aliases produced different lock keys for one simulator (screenshot-status-bar.ts:213). Resolved at the address owner, not at the key. packages/platform-apple/src/core/simctl.ts now resolves the device set to one canonical identity (canonicalSimulatorSetPath, via realpath with a path.normalize fallback), and both simulatorAddressFor and scopeSimctlArgs use that value, so the lock/cache key and the --set argv can never disagree. Naming the default root (~/Library/Developer/CoreSimulator/Devices) now answers as the default set, i.e. the same identity as omitting --set. Host authority stays behind the host-kit file port (canonicalHostPathSync), which the R13 substrate rule requires. Covered by one simulator reached through two spellings of its device set keeps one window (a real symlink) and naming the default device set explicitly shares the default window; both fail at 6d59c2b3e5.

P2 — a timed-out settings write left a stale known-cleared answer (app-settings.ts:115). invalidateSimulatorStatusBarOverrideCache(device) now runs before the command in both the wifi and airplane legs, not after it: a write that hits its deadline may still have reached simctl, so no cached answer survives the attempt either way. Covered by a timed-out settings write forces the next capture to read the bar again, which asserts the next capture issues status_bar <udid> list and restores the setting the timed-out write applied; it fails at 6d59c2b3e5 with "must read the bar, not trust a cached answer".

P3 — the settings wait and timeout were undocumented. One line added to the owning settings --help text (packages/command-surface/src/capture/settings.ts) and one bullet to the command reference, plus one bullet on the screenshot side. Both state the wait, that the 10-second budget covers the write and never the queue wait, and that a cancellation during the wait changes nothing.

Gate. pnpm check:affected --run --head 6f7b9ead18: exactCommit=true, unit 16764 passed / 0 failed, vitest-related 317 / 0, layering, command-docs, docs-prose and docs-links passed. Live re-check on e2e-apex (iOS 26.2) with this worktree build, because the settings route changed: two gated normalized captures restored the seed, a plain capture saw the caller's bar, settings wifi waited 2.9 s and survived the restore, a cancelled write rejected in 401 ms without touching the bar, and a capture through a symlinked device-set path wrote and restored one simulator's bar. Override cleared afterwards.

Thread 4435764303 (caller-value recovery after a failed clear) is replied to and resolved as a pre-existing follow-up; the comment at screenshot-status-bar.ts:177 is now explicitly limited to the already-known-cleared case.

@thymikee thymikee left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Round-2 review of 6f7b9ead18328ac4dd3c5a2ed0bba8d5a9b878eb.

The round-1 alias finding is resolved: canonical identity feeds both the simulator lock/cache address and --set. The wifi/airplane cache is now invalidated before each write, so timeout ambiguity cannot preserve a stale cleared answer. The settings help and commands page describe the same wait and 10-second write budget. The host-kit port is appropriately narrow for keeping filesystem ownership outside platform-apple, but its use raises the following issues.

Blocking findings:

  • P1 — Explicit simulator set can lose its scope. In packages/platform-apple/src/core/simctl.ts:31-44, if realpathSync fails for either candidate, canonicalExistingPath returns a merely normalized absolute path; the code still compares it to the process-home default and may return undefined. Then scopeSimctlArgs omits --set. An explicit caller-selected set (including a symlinked path) can silently become simctl's ambient default. Keep explicit paths scoped unless successful canonicalization proves they are the default, and avoid process-home authority where the request host differs. This also means intended symlink aliases do not canonicalize when their target does not yet exist; failure should preserve explicit scoping rather than erase it.
  • P1 — Nested settings writes can still be undone. withSimulatorStatusBarHeld bypasses the lock when the async context already holds its key (screenshot-status-bar.ts:136-145). If wifi/airplane is invoked from a normalized capture's context, the setting write runs immediately and the outer capture's finally restores its older snapshot over it. The new serialization only protects callers outside that context; add a write-safe re-entry path or otherwise prevent restoring a snapshot over a later setting write.
  • P2 — A failed restore override leaves a false known-cleared cache. If the restore clear succeeds but re-applying the caller's overrides fails, restoreSimulatorStatusBarOverrides exits before invalidating the cache (screenshot-status-bar.ts:184-194). If that key was previously known-cleared, the next capture skips reading and treats the leftover deterministic bar as cleared, losing the caller's overrides. Distinguish failed clear from failed re-apply and do not retain a cleared answer for the latter.
  • P2 — Cancellation after entering the status-bar transaction is swallowed. Setup errors are diagnosed and swallowed, and restore errors are also diagnosed and swallowed (screenshot-status-bar.ts:162-170, 110-113). Once a normalized capture has entered, abort-triggered simctl failures can therefore return a screenshot success despite cancellation. Propagate cancellation from the owned signal while retaining best-effort restore.

The host-kit canonicalHostPathSync port itself is narrow and correctly avoids direct node:fs in platform-apple. However, canonicalization currently performs synchronous filesystem resolution twice per simctl argument build, including the unchanged default root on hot capture paths. This is a material event-loop cost, not an incorrect ownership boundary; cache the default identity and avoid unnecessary resolution. Missing/nonexistent paths cannot be resolved with realpath and should remain explicit.

Bot-thread verdicts (15 open threads; no replies posted):

  1. screenshot-status-bar.ts:173 — real defect, blocking: failed restore override can leave stale known-cleared state and lose caller overrides (P2 above).
  2. capture/screenshot.ts:74 — not a defect: commands.md now documents the wait and cancellation behavior.
  3. app-settings.ts:107 — not a defect: settings cliDetail and commands.md now document the settings wait.
  4. screenshot.ts:252 — not a defect: naming nit only; predicate accurately selects simulator status-bar captures.
  5. app-settings.ts:130 — real defect, blocking: re-entrant airplane/wifi write can be overwritten by the outer capture restore (P1 above).
  6. screenshot-status-bar.ts:61 — not a defect: bounded command windows can delay queued work, but the capture includes its required operation; the suggested 90s framing sums separate per-step caps and does not establish an unbounded command. The queue wait is intentionally outside the 10s write budget.
  7. screenshot-status-bar.ts:223 — not a defect: a failed snapshot already cannot safely restore unknown overrides; the timeout bounds that existing best-effort probe.
  8. screenshot.ts:261 — not a defect: comment wording nit; the reservation covers the status bar regardless of backend path.
  9. commands.md:761 — not a defect: timeout may leave the setting applied, but the command correctly invalidates cache before running and returns the command error; docs do not promise rollback.
  10. simctl.ts (cubic-dev-ai) — not a defect: it requests using hostHomeDirectory(), which this code already does via host-kit.
  11. screenshot-status-bar.ts:96 — real defect, blocking: cancellation from an in-progress setup/restore can be swallowed (P2 above).
  12. simctl.ts:22 — real defect, blocking: process-home comparison can erase an explicit set scope; additionally fallback comparison after failed canonicalization is unsafe (P1 above).
  13. simctl.ts:32 — not a defect: synchronous canonicalization overhead is real and merits caching, but is a performance concern without demonstrated correctness impact.
  14. capture/settings.ts:105 — not a defect: code, CLI help and commands.md currently agree on 10 seconds; drift risk alone is not a present behavior mismatch.
  15. app-settings.ts:128 — not a defect: a plain screenshot must wait while the status bar is being modified so it cannot capture an in-progress write; this is the reservation's intended serialization.

Verdict: needs changes.

@thymikee thymikee left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Round-2 independent review: needs-changes.

Reviewed 6d59c2b3e5680fd7ea84ce09891b610c3fd1a923 → 6f7b9ead18328ac4dd3c5a2ed0bba8d5a9b878eb, plus the affected callers and all 15 open bot threads returned by GraphQL reviewThreads (pagination complete). Source remained read-only.

P2 — Derive default-set identity from the executing host before removing an explicit --set. packages/platform-apple/src/core/simctl.ts:22-36 reads hostHomeDirectory() (process os.homedir()), then turns an explicit path matching that directory into undefined. However, daemon commands execute inside withCommandHost({ host, requestEnv }, ...) (packages/daemon/src/server/request-router.ts:222-224), and children receive that host's environment (packages/host-kit/src/internal/exec.ts:157-160). Those homes can already differ in the current architecture; this is not dependent on a future host feature. A source-executing probe with the bound host's HOME=/tmp/.../host-home and the explicit requested set /Users/thymikee/Library/Developer/CoreSimulator/Devices returned simctlListInventoryArgs(...) === ['list', '-j']: the caller's explicit set was discarded based on the unrelated process home. A host/provider whose unscoped default differs can therefore list or operate on a different set, or fail to find the requested UDID. In the reverse direction, the host's actual explicit default and its implicit default can retain different lock/cache keys. Resolve the default identity from the same host authority used to execute the command; when that authority cannot establish the default, retain the explicit set instead of guessing from the process home. Keep explicit canonical argv and implicit-default identity consistent without erasing the caller's scope based on another home. Add a differing-host-home regression case. This confirms the core of bot thread 4436131110; its extra realpath-failure scenario is not needed to establish the finding.

The three round-1 findings:

  • Aliases: resolved for ordinary existing local sets, with the host-default defect above still blocking full closure. The real symlink and /. probe produced the same canonical address and the same canonical --set argv. canonicalHostPathSync belongs in host-kit's existing file port: it is one narrow filesystem primitive, and Apple-specific default-set policy remains in platform-apple, as R13 requires. For a missing/unresolvable set, the catch retains an absolute normalized explicit path and does not create anything; simctl still owns the eventual success/failure. That fallback does not establish filesystem identity when realpath is unavailable, so the claim is not unconditional. For a stable, ordinary symlink to an existing set, sending the target path reaches the same directory; I found no demonstrated regression merely from deliberately using such a symlink. Erasing an explicit default based on the wrong home is the demonstrated behavior change.
  • Stale known-cleared cache: resolved. Wifi and both airplane branches invalidate inside withSimulatorStatusBarHeld, before the command; airplane-off remembers cleared only after success. A separate capture cannot read the invalidated cache or half-applied write while that same key is held. The command settles before release, including timeout rejection. This removes the specific apply-then-timeout stale-cache failure. No production capture callback calls setIosSetting; the hypothetical re-entrant write from round 1 does not become a reachable regression here.
  • Wait/budget docs: resolved. settings.ts:105, commands.md:761, and the screenshot bullet at commands.md:1046 describe the wait and cancellation correctly. STATUS_BAR_COMMAND_TIMEOUT_MS bounds the actual status-bar write after acquisition, not queue time or the whole request. Cancellation during the queue prevents the write. Timeout after acquisition does not promise rollback; the additional clarification suggested below is useful.

Hot-path assessment: for an explicit nondefault set, canonicalization performs two synchronous realpath operations each time it runs. A normal buildSimctlArgsForDevice canonicalizes at both simulatorAddressFor and scopeSimctlArgs, so it can perform four, with further calls for cache/lock keys. An implicit default returns before filesystem work. This is real repeated synchronous I/O, not a cached address; xcrun's timeout starts afterwards and does not bound a slow filesystem lookup. I have no measured local-filesystem latency regression to classify as another blocking finding. The bot's suggested lexical “outside home” shortcut would be incorrect for symlinks, and process-wide memoization would need to account for host identity and paths created/rebound later.

One-line verdicts for all 15 open bot threads (no thread replies posted):

  1. 4435811817 — failed restore / known-cleared: Not a defect as stated — a real-overrides snapshot is read only when the key is absent, and a successful restore-clear removes normalization before the override; the claimed simultaneous known-cleared state and leftover deterministic bar do not follow. Caller-snapshot recovery after failed restore remains the already-dispositioned, pre-existing follow-up.
  2. 4435811819 — missing user docs: Not a defect on this head — settings help plus commands.md:761,1046 now document both waits and queued cancellation.
  3. 4435811821 — missing settings help: Not a defect on this head — packages/command-surface/src/capture/settings.ts:105 contains the requested wait and execution-budget explanation.
  4. 4435811824 — predicate naming: Not a defect — a naming preference with no incorrect predicate or triggering behavior.
  5. 4435903514 — nested airplane restore: Not a production defect — independent requests queue; hold-only re-entry does not invoke normalization, and there is no production capture → settings call edge to trigger the hypothetical enclosing restore.
  6. 4435903518 — long total hold: Not a defect — command budgets deliberately bound steps rather than the transaction/queue; both plain screenshot waiting and post-wait settings budgets are documented, and rejecting overlap requires the shared hold.
  7. 4435903523 — snapshot timeout loses overrides: Real defect, nonblocking pre-existing recovery policy — a failed snapshot, including the newly bounded slow read, enters canRestore:false and later clears unknown caller overrides; the base already deliberately normalizes after read failure, and a longer timeout only moves that failure boundary. Track preservation/fail-closed behavior separately from the reviewed overlap fix.
  8. 4435903524 — density-lock comment: Real defect, nonblocking P3 — screenshot.ts:266-267 attributes density coverage to the inner runner reservation, but its callback ends at runner capture; describe the outer fallback-flow hold instead (direct runner capture performs no density step).
  9. 4436090997 — timeout outcome docs: Real defect, nonblocking P3 — the newly documented timeout should also tell callers that the bar may already have changed; only cancellation while queued guarantees no write.
  10. 4436091001 — direct os.homedir import: Not a defect on this head — this is the one outdated open thread; the direct node:os use was replaced by the host-kit port (the separate bound-host semantics issue is item 12).
  11. 4436131105 — active cancellation swallowed: Not a defect as stated — status-bar setup/restore intentionally receive no request signal, and help expressly promises cancellation while waiting; throwing from a restore catch would jeopardize cleanup without fixing the asserted path.
  12. 4436131110 — wrong home authority: Real defect, blocking P2 — process home can differ from the bound executing host, so removing explicit --set and deriving its default lock identity from it is unsafe; see the reproduced finding above.
  13. 4436131112 — repeated synchronous realpath: Not an established defect — the repeated synchronous I/O is real for explicit sets, but not every command, and no material local latency regression is demonstrated; proposed permanent caching/lexical filtering would require additional correctness constraints.
  14. 4436131115 — literal timeout in help: Not a defect — the documented 10 seconds matches the owning constant; hypothetical future drift does not justify hiding an observable limit or importing platform implementation into command-surface.
  15. 4436131118 — plain capture waits on settings: Not a defect — status_bar clear/override changes the visible bar a plain screenshot photographs, not merely its cache; sharing the reservation is deliberate.

Bot total: 4 real-defect threads: 1 blocking P2, 2 nonblocking P3 documentation issues, 1 nonblocking pre-existing snapshot-failure policy. The last is not a new round-2 finding.

Validation: static source/caller/test review, git diff --check (passed), and one standalone Node probe executing the source modules with workspace import resolution. The probe exercised a real symlink, /., a missing path, and a bound command host with a different HOME; it did not invoke simctl or any device. No package test suite, install/build, device operation, or CI wait was performed. Author-reported gate/device results were read as reports, not independently reproduced evidence. Approval remains blocked by the host-default P2.

@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 9 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/host-kit/src/internal/exec.ts Resolved
Comment thread packages/host-kit/src/internal/exec.ts Resolved

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 8 new findings on d4a6670. The verdict and the full list are in the summary comment.

Comment thread packages/host-kit/src/host-file.ts Resolved
Comment thread packages/host-kit/src/internal/exec.ts Resolved
Derive the default device root from the bound command host's environment instead of this
process's home, so an explicit `--set` is only ever recognised as the default when the host
that will execute `simctl` owns that root. When no host is bound, or that host names no
absolute home, keep the caller's explicit set rather than guess from another home. The
host-environment home lookup moves to the kernel owner of the environment type, where every
caller already reaches it.

Describe the density step as covered by the fallback flow's own hold, and tell settings
callers that a timed-out write may already have changed the bar: only cancelling while the
request is queued guarantees no write.
@thymikee
thymikee force-pushed the fix/sm11-status-bar-restore branch from d4a6670 to 5ffe9ec Compare November 1, 2026 15:45

@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/kernel/src/source-value.ts Resolved

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 4 new findings on 5ffe9ec. The verdict and the full list are in the summary comment.

Comment thread packages/host-kit/src/internal/exec.ts Resolved
@thymikee

thymikee commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

Round 3 addresses review 5728727416 at new head 5ffe9ecf54. The blocking P2 is fixed test-first; the two non-blocking P3s are folded into the same commit; all 15 bot threads are replied to and resolved.

Blocking P2 — simctl.ts derived the default set from the process home (bot 4436131110). defaultSimulatorSetPath() now reads the bound command host's environment through commandHostHomeDirectory(), the same authority resolveSpawnEnv hands the child. An explicit --set is erased for the default only when the host that will execute simctl owns that root; with no bound host, or a host naming no absolute home, the caller's explicit set is kept. The host-env home lookup moved to @agent-device/kernel/source-value (the owner of EnvMap), which kept the eager-closure gate green — my first attempt widened a hot closure and that gate failed it. Fails at 6f7b9ead18, passes at 5ffe9ecf54:

  • an explicit set is kept when the executing host names a different home (platform-apple/.../simctl.test.ts), with the reverse assertion that a host whose HOME is the default root answers as the default set;
  • commandHostHomeDirectory answers with the home the spawned commands inherit (host-kit/.../exec.test.ts) — host value, request override, missing home, non-absolute home.

P3 — density-lock comment (bot 4435903524). captureScreenshotViaRunner now describes the outer fallback-flow hold: its own reservation covers the runner capture alone, the later density step is covered by the fallback flow's hold, and a direct call performs no density step. Comment-only.

P3 — timeout outcome docs (bot 4436090997). The settings help text and commands.md now state that a write hitting its 10-second deadline may already have changed the bar, and that only cancelling while queued guarantees no write.

Other 13 bot threads — each replied with the reviewer's one-line verdict and resolved: 4435811817 not a defect as stated (caller-snapshot recovery stays the pre-existing follow-up); 4435811819 / 4435811821 not a defect on this head (docs landed); 4435811824 naming preference; 4435903514 not a production defect (no capture→settings edge); 4435903518 budgets bound steps deliberately; 4435903523 replied "follow-up, pre-existing on base" and resolved; 4436131105 cancellation while waiting is what help promises; 4436131112 repeated realpath real but not an established regression; 4436131115 literal matches the owning constant; 4436131118 sharing the reservation is deliberate; 4436091001 outdated — host-kit port in place.

Gate. pnpm check:affected --run --head 5ffe9ecf54: exactCommit=true, unit 16768 / 0 failed, vitest-related 1220 / 0, layering, eager-closure budgets, command-docs, docs-prose, docs-links passed. Receipt .tmp/validation/run-H0sXt0/receipt.json.

Live check on e2e-apex (iOS 26.2) with this worktree build, since the argv for a default-set spelling changed: bound-host.mjs — host HOME at the real default root → ["simctl","list","devices","-j"] and the real command lists the requested UDID; host HOME /tmp/another-host-home → ["simctl","--set","/Users/…/CoreSimulator/Devices",…] and that also lists it (before this fix the --set was erased). set-alias.mjs — aliased and default-root captures both restored the seed exactly. simctl status_bar clear after; the temporary symlink removed.

@thymikee

thymikee commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

Coordinator check of round 3 (6f7b9ead18..5ffe9ecf54), in place of a third full review per the round cap:

  • The blocking P2 is resolved. defaultSimulatorSetPath() now reads the bound command host's home through commandHostHomeDirectory(), which merges host.env and requestEnv the same way resolveSpawnEnv does. When no host is bound, or the host has no absolute home, the explicit set is kept. Nothing falls back to the process home.
  • Accepted trade-off: outside a bound host, an explicit default-root --set and an implicit default produce two lock keys for the same simulator. That is the fail-closed behavior the round-2 review asked for. Inside the daemon a host is always bound.
  • Moving hostHomeDirectoryFromEnv to kernel/source-value (driven by the eager-closure budget) is a pure function move with its contract unchanged. The vega and runtime-host import updates are mechanical.
  • The P3 comment and docs fixes match the code.

No blocking findings. All findings from rounds 1–2 are resolved.

@thymikee

thymikee commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

I found no code defects at 5ffe9ec, and the findings from the earlier review are now fixed. Live proof is still missing: I did not run the new simctl and exec regression tests, so the fail-before claim is unverified. I also did not reproduce the live set-identity run. The overlapping-capture status bar run was owner-direct in the earlier round, not through the daemon. I did not verify that CoreSimulator resolves its default set from HOME rather than from the user record, so the default-root erase rests on that assumption. On the daemon route, host HOME equals the user's home, so behavior does not change there. CI is green, with 21 checks and none failing at this commit. There are no conflicts. Before merge, please answer the design question below.

Not blocking, and you can take or leave it: canonicalSimulatorSetPath now feeds simulatorAddressFor, which every set-scoped consumer reads, not only the status bar key. A device record with an explicit simulatorSetPath that names the host's default root, or an alias of a set, now behaves differently. foldable/orientation.ts:61 now rotates it where it used to refuse with unsupported-device-scope. display-inventory.ts:118 now reads the CoreDevice panel table where it used to read the guest panel table. runner-device-set.ts:22-26 now drops -DVTSimulatorSetLocation. isSameRunnerSimulator now treats alias spellings as one runner. One sentence in the PR body naming these consumers would be enough.

Could the design be simpler? Key the status bar lock and the known-cleared cache by platform:kind:udid, as the first heads of this PR did, and revert simctl.ts, the commandHostHomeDirectory and canonicalHostPathSync ports, and the hostHomeDirectoryFromEnv move to main. CoreSimulator mints a UUID per device, so distinct sets hold distinct UDIDs. Only a copied set directory can repeat one, and there a shared lock only over-serializes, which is harmless. The cache hazard needs a copied set plus overrides on one copy. Set aliasing then cannot split one simulator into two windows. The rounds of guards (set in key, then realpath, then default-root erase, then process home, then host home, then empty or trimmed HOME, then the open nonexistent-symlink case) all disappear, and no other simctl consumer changes behavior. Do you accept a shared window for UDID-colliding copied sets? If set-scoped identity must stay, please keep the current shape and close the nonexistent-target alias gap at canonicalExistingPath.

On the open inline threads: no P1 or P2 thread still applies. Four lower-priority threads still apply, and none of them blocks. Please resolve these, which no longer apply:

@thymikee

thymikee commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

Implemented the maintainer's design simplification at f01442f6e58ba12d4b5a296f6cc2b200422d59c2, following the coordinator's decision.

  1. Identity: the status-bar transaction lock and known-cleared cache both use platform:kind:udid. Set aliases cannot split one simulator into two windows. UUID-colliding copied sets share the lock/cache; over-serialization is accepted.
  2. Reverts: git diff origin/main -- is empty for simctl.ts, simctl.test.ts, host-kit command.ts, host-file.ts, internal/exec.ts, internal/exec.test.ts, internal/host-file.ts, kernel source-value.ts, Vega vega-executable.ts, and src/platform-runtime-host.ts. Canonicalization, default-root elision, both added host ports, and the helper move are gone. Orientation, display inventory, runner set arguments, and runner identity therefore retain main's behavior.
  3. Retained: the whole capture/restore transaction, plain-capture hold, Wi-Fi/airplane hold and budget, invalidation before settings writes, cancellation policy, and help/docs.
  4. Tests: the adapted same-UDID/different-set-path test failed first on 5ffe9ecf54 because both capture callbacks entered; it passes after the key change. Removed canonicalization-only tests. The five focused suites pass 80/80. pnpm format passed. The warm pnpm check:affected --run --head f01442f6e58ba12d4b5a296f6cc2b200422d59c2 passed all runnable stages: 16,762 unit tests passed, 1 skipped; 317 related tests passed. Receipt .tmp/validation/run-vmKvOD/receipt.json: exactCommit=true, checkoutChanged=false. Static, build and documentation gates passed.

The first exact-head gate failed only in apple-platform-output-guard.test.ts:385 (record exceeded 20 seconds): 316 passed, 1 failed. The unchanged-base reproduction on PR #4131 matches the test, line, timeout and assertion. The subsequent isolated full guard passed 3/3; the requested iOS-only warm retry passed 1/1 (2 unrelated tests skipped). No recording implementation or deadline was changed.
5. Threads: all 19 listed no-longer-applicable threads plus the four remaining lower-priority threads received a one-line disposition and are resolved. Realpath and nonexistent-target concerns are moot after removing path identity; the airplane-off invalidation policy stays as requested. This round had a cold diff self-review; no new independent reviewer was used.

(1) Real daemon route, one session — production route evidence

Device: host local, e2e-apex, UDID 6832D598-6CC2-49DF-9623-64CBF79ACB35, iOS 26.2. The daemon ran this worktree's built dist/src/internal/daemon.js; commands used the T3 device_open-bound CLI with an isolated HTTP daemon config and session sm11-a.

Seed setup: xcrun simctl status_bar <udid> override --dataNetwork lte --wifiMode active --wifiBars 2 --cellularMode active --cellularBars 4 --operatorName 'SM11 Carrier'.

A screenshot ... --normalize-status-bar followed by status_bar ... list restored this seed byte-for-byte:

DataNetworkType: 8
WiFi Mode: 3, WiFi Bars: 2
Cell Mode: 3, Cell Bars: 4
Operator Name: SM11 Carrier

Then another normalized screenshot ran while settings wifi off was sent in the same session. The temporary WiFi Mode: 3, WiFi Bars: 3 normalization was observed before sending settings. The daemon serialized the work. Its event log, UTC:

21:18:36.174 request.started screenshot
21:18:37.688 request.started settings
21:18:38.660 request.finished screenshot ok
21:18:39.657 request.finished settings ok

Final xcrun simctl status_bar <udid> list:

DataNetworkType: 8
WiFi Mode: 2, WiFi Bars: 3
Cell Mode: 3, Cell Bars: 4
Operator Name: SM11 Carrier

LTE, cellular overrides, and carrier survived; Wi-Fi remained off after the capture. CoreSimulator chooses the bars count for the mode-only settings command, so the assertion checks Wi-Fi mode. Two earlier evidence-script attempts incorrectly pinned that bars count; their assertions failed, were corrected, and their logs were retained. production-restored.txt separately proves exact seed restoration.

(2) Two-capture overlap — isolated daemon harness evidence

The daemon's per-device claim already prevents two sessions on one device, so overlapping capture transactions are reachable only through direct platform callers and this harness. No production claim checks or source code were weakened.

The harness loaded the source modules at the named head and started two isolated embedded HTTP daemon runtimes in one process, each with a seeded session (harness-a, harness-b). Their independent daemon execution locks allow overlapping requests while the platform's actual status-bar owner stays shared. Both screenshot ... --normalize-status-bar commands used the bound CLI against those HTTP endpoints. A harness-only tool-provider gate held the first capture after its real simulator screenshot; a wrapper logged platform capture entry without replacing capture behavior.

21:22:07.713 platform capture A entered
21:22:10.439 real simulator screenshot A completed; capture held
21:22:10.755 screenshot B sent over HTTP
21:22:10.990 platform capture B entered while A remained held
21:22:11.031 both capture calls overlap; release A
21:22:13.173 screenshot A returned, exit 0
21:22:16.139 screenshot B returned, exit 0

Only one caller-state read occurred while A was held. Both eventual status_bar list reads returned the seed above, and the final listing equaled the seed byte-for-byte. An intermediate attempt to strengthen the logging used the wrong composition property and timed out; after correcting the harness instrumentation, this run passed. This harness is not production route evidence.

Cleanup and artifacts: both live runs finished with status_bar ... clear, then list showing only the empty heading. All three sessions and all daemon runtimes were closed; the simulator remains booted. Local scripts, PNGs, raw outputs, failed-attempt logs, and timelines are retained under /private/tmp/sm11-daemon-live/ (production.py, production.log, harness.mjs, harness-final.log, harness-final/timeline.json). The production daemon's stop helper could not verify retirement; its exact launched PID/start-time/command were checked and SIGTERM completed with exit 0.

Parent-relative round: production 9 files, +23/-97; tests 3 files, +6/-246; docs 0. Final PR: 12 files, production +263/-78, tests +1179/-135, docs +2/-0. At publication, GitHub shows the new head with 17 checks queued/running and 2 successful; coverage/integration remain GitHub-owned. This is not a merge-ready claim.

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 5 new findings on f01442f. The verdict and the full list are in the summary comment.

@thymikee

thymikee commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

Handled all six threads in one help/docs-only round at be0846f81c868339e01a1faff6047ffe1450acee (parent f01442f6e58ba12d4b5a296f6cc2b200422d59c2).

  1. r4437500991 — docs fixed, timeout unchanged. At the reviewed head, packages/command-runtime/src/commands/settings/run-settings-command.ts, withRetryPolicy, and MIN_RETRY_COMMAND_TIMEOUT_MS do not exist. The current route is snapshot-settings.ts:executeSetSetting → bound runtime → setIosSetting. Settings uses DEFAULT_TIMEOUT_POLICY: a 90,000 ms standalone CLI request envelope, confirmed by invoking resolveCommandRequestTimeoutMs(resolveCommandTimeoutPolicy('settings'), {}). That envelope includes waiting. Help and commands.md now state this separately from the 10-second status-bar write limit and preserve the unknown-outcome/cancel-while-queued distinction. No retry budget or runtime behavior changed.
  2. r4437501000 — default checked, budgets documented. The built-in runXcrun path has no default per-command timeout. It forwards options.timeoutMs unchanged; the provider probe observed undefined when omitted. runCmd uses normalizeTimeoutMs(undefined) === undefined and arms no timer. Retained the explicit budgets and documented 10 seconds per status-bar read/preparation command, 30 seconds per restore command, and up to 60 seconds for clear + reapply. The request can expire before those steps finish.
  3. r4437500993 — intentional, resolved. The coordinator and maintainer explicitly selected platform:kind:udid; UUID-colliding copied sets may over-serialize. The PR body already records that accepted trade-off. No set-path identity is reintroduced.
  4. r4437500759 — fixed. Added the paragraph separator before the status-bar paragraph. The separator before Settings: was already present and remains. The rebuilt CLI help confirms both blank paragraph boundaries and the 90-second request wording.
  5. r4437500996 — retained with reason. runnerOpts.signal is execution context; shared SettingOptions is cross-platform setting data. Folding cancellation into that contract mixes execution context with setting data; a one-field trailing bag would still leave seven parameters. The platform-local signal remains restricted to the Wi-Fi/airplane queue wait.
  6. r4437501006 — rejected with source/test evidence. acquireKeyedLock's abort listener rejects only while !entered; after entry it waits for and returns the task result. It does not race cancellation against an admitted callback. Existing an abort after lock admission waits for the running task to settle and nested-capture-after-abort tests pass. Runner cancellation remains the runner command's responsibility. No capture route changed.

All six threads have replies and are resolved. Validation: pnpm format and five focused suites pass (69 tests). pnpm check:affected --run --head HEAD resolved to this head and passed all runnable stages: 16,762 unit tests passed (1 skipped), 317 related tests passed, plus format, lint, typecheck, layering, export checks, build, and documentation gates. Receipt .tmp/validation/run-Q1LUrs/receipt.json: exactCommit=true, checkoutChanged=false.

Parent-relative scope: 2 files, +4/-3 (help data: +2/-1; command reference: +2/-2); no behavior or test changes. Cold self-review completed; no new independent review. No live run was needed because capture routing and execution are unchanged. The separately labeled production/harness evidence remains attributed to f01442f6. CI coverage/integration remain GitHub-owned at the pushed head.

@apex-sentinel-staging apex-sentinel-staging 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.

Apex Sentinel: 4 new findings on be0846f. The verdict and the full list are in the summary comment.

@thymikee

thymikee commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

The changes in be0846f look good. The help and docs now separate the 90 s request timeout, which includes the wait, from the 10 s write limit, and the missing blank line before the status-bar paragraph is fixed. This answers the simplicity challenge from the earlier review (#4136 (comment)).

All 21 checks pass at this commit. I did not run the focused suites or the check:affected gate, so the pass counts are yours. I did not repeat the live daemon-route run either. It was taken at f01442f, and be0846f differs from it only in help and docs, so it covers this head. There are no conflicts.

None of the earlier inline threads from the other review still applies. These two are fixed at this head: the blank line in settings.ts (#4136 (comment)) and the timeout wording in the help and commands.md (#4136 (comment)). These are benign by design or outside this change, and they are already resolved: the udid-only lock key (#4136 (comment)), the positional signal on setIosSetting (#4136 (comment)), the explicit xcrun timeouts (#4136 (comment)), abort handling after admission in the keyed lock (#4136 (comment)), cache sharing across copied sets, which was already the behavior on main (#4136 (comment)), the lock window over the container copy (#4136 (comment)), the help figures matching the constants (#4136 (comment)), and re-entry into withNormalizedSimulatorStatusBar, which has no production caller (#4136 (comment)).

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Nov 1, 2026
@thymikee
thymikee merged commit 8ea13d7 into main Nov 2, 2026
21 checks passed
@thymikee
thymikee deleted the fix/sm11-status-bar-restore branch November 2, 2026 06:37
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