Skip to content

fix(android): keep close --shutdown's IME restore out of the settings flush window - #3331

Merged
thymikee merged 21 commits into
mainfrom
fix/android-shutdown-ime-flush-window
Oct 9, 2026
Merged

thymikee merged 21 commits into
mainfrom
fix/android-shutdown-ime-flush-window

Conversation

@thymikee

@thymikee thymikee commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Summary

close --shutdown left an Android emulator restarted with the agent-device test IME as its default keyboard (#3318). The restore itself succeeded — default_input_method read back correctly while the emulator was still running — but AOSP's SettingsProvider persists setting changes asynchronously (delayed XML flush, capped at 2 s in SettingsState.java), and the close finalizer ran adb emu kill immediately, before settings_secure.xml was rewritten. The reboot reloaded the stale file with the helper IME still default.

The fix, enforced at both ends of the hazard: the one inner function that issues a restore’s ime set registers the device’s flush window (write + 2.5 s, timed on a monotonic per-serial clock — the window is a property of the device’s SettingsProvider, not of any host state dir) in the registrar’s finally for every issued emulator write whatever the outcome, because a readback mismatch cannot prove the provider never accepted the write; and the one executor of adb emu kill waits that window out before killing. Whether a write confirmed by read-back is a separate fact the mark carries, and it gates only one decision: whether an uninspected close may retire another session’s durable recovery marker. So every kill path is gated — close --shutdown, the standalone shutdown command, and any future caller. A close cancelled mid-wait never reaches the kill and hands the remaining window to the next kill-bound caller; overlapping windows coalesce into one wait because the provider rewrites the whole settings file per flush. Touched files: 10.

Validation

Per-head validation lives in comments: the latest validation comment records the tested head and its results (pnpm check:affected --run, pnpm typecheck, pnpm fallow audit --base origin/main). New regression tests pin: restore-before-kill ordering with an awaited deferred restore, the flush deadline with signal forwarding, the marker surviving the settle, an abort mid-settle retaining marker + window with the retry consuming the remaining wait, coalescing of overlapping windows into a single wait, a cross-session kill-bound close waiting a window another close registered, a never-activated close leaving a retained marker untouched, a never-activated close covering an issued-but-unconfirmed window killing but keeping the marker, no settle on ordinary close / physical-device / failed-restore paths, the kill-site gate (kill waits a registered window / skips with none / abort never reaches adb emu kill), and startup-orphan recovery registering the window while paying no boot sleep.

Live device evidence: on an arm64 API 35 (google_apis) emulator, head cycles run open com.android.settings --relaunch (helper becomes default) → close --shutdown (~2.9 s, settle included) → -no-snapshot-load restart → LatinIME, 5/5. The base commit was then run on the same AVD as a control and does not reproduce there either (LatinIME 3/3, close in ~0.3 s), so this emulator cannot discriminate the fix — it proves no new breakage only. The reproduction stands on the reporter's platforms (API 32/37 x86_64). Falsification of the hazard itself on this image: a helper ime set followed by SIGKILL of qemu within ~1 s did strand the helper across restart, confirming the lost-flush window exists here too; graceful adb emu kill just usually wins the race on this fast image. A base run on API 32/37 is the missing discriminator and needs a provisioned AVD on those images.

Review history, with the defects each round found in shipped code: round one resolved three blocking findings (test type errors, format gate, abort-signal gap in the settle); round two found that an aborted settle still cleared the marker, letting a retry kill inside the flush window (10b4126e5); round three found that round-two's refactor routed the never-activated close through the shared marker clear, erasing a retained orphan marker (6c5221baf, split not-activated-here from no-record at the reason type); round four (cubic) found the double-window lock hold in the round-two/three design, fixed by coalescing to one deadline (3bbda232d); round five (independent review) found that an ordinary close registered no window, leaving a second session's close --shutdown free to kill inside the flush window — every confirmed restore now registers its window, and the kill-bound predicate has a single owner (fcf968b22); round six (cubic) found that daemon-startup orphan recovery called the inner restore directly and registered no window, and that the standalone shutdown command's kill was never gated by any close-side wait — registration moved inside the inner restore and the flush hold moved to the kill-site itself (83e78a7b8); round seven (cubic) found that the window guard timed itself on the wall clock, letting a forward host-clock step release the kill inside the flush window — the window is now timed on the process monotonic clock — encoded in the map name, testImeLastRestoreAtPerfMs — and the kill-site wait tests assert the derived magnitude and the abort/retry remainder re-derivation (5753cfa45, c352648db); round eight (cubic) found that the cancelled-kill test passed on any rejection rather than the cancellation it claims — it now binds the rejection by identity to controller.signal.reason alongside the AbortError name (44ca81028, ab7334854); round nine (cubic) found the residual race in the wait itself: it read the mark once and reported covered even when a restore landed while it slept, releasing the kill inside the newer window — the wait now re-reads after every sleep and loops to the newest deadline, and the kill-site tests observe the middle of the wait with deferred sleeps (91d4c0b0a); round ten (cubic) confirmed that a daemon restart inside the window forfeits the remaining wait for a dead process's write — classified as a cross-process-deadline design boundary (a persisted deadline must be clock-independent and carry an expiry rule, reopening round seven's clock class otherwise) and filed as follow-up #3346, with the boundary recorded in the state module's doc comment (7c8297593, ce40232e3); round eleven (cubic) found that a kill racing an in-flight restore read idle — the window opens when the device accepts ime set, but the mark registered only after the readback — so in-flight writes are now tracked from issue and drained by the kill-side wait each round, the tests' hardcoded window literals moved behind an exported constant, and two local-only gate misses (an oxlint spread, a complexity threshold) were repaired after GitHub surfaced them (4d1b6d094, 248aa0b35, b0516feb8); round twelve (coordinator) demanded the reachability proof, which produced a self-corrected ledger — proven interleaving is fire-and-forget startup recovery vs an early kill, with weaker pairs labeled candidate/open in-thread; round thirteen (coordinator) found the pending-write design had silently encoded "readback mismatch ⇒ no provider write" — the mark now registers in the registrar's finally for every issued emulator restore, whatever the outcome (b79910d46); round fourteen (cubic) found the finally-registration had silently inverted the marker-clear rule’s premise — the mark carried a timestamp alone, so a later uninspected kill-bound close covering a set-failed restore’s mark cleared the durable recovery marker while the helper was still active — the mark now carries its write’s own provenance, the flush wait reports covered-issued vs covered-confirmed, and the clear rule consumes that fact instead of inferring it from coverage (663f11d8c); round fifteen (cubic) collapsed a byte-identical mark seeder duplicated across two test files into the shared IME fixtures module, where the next mark-shape change lands in one edit, and tightened the marker-retention test’s sleep assertion from any-positive to the file’s own nearly-full-window bound (b36626a4f).

Unresolved risk: the timed wait assumes the AOSP 2 s flush cap; a device overriding SETTINGS_PROVIDER_MAX_WRITE_DELAY_MILLIS upward could still lose the write.

Closes #3318

View guided diff Turn on auto-fix

… flush window

AOSP SettingsProvider persists a setting change asynchronously (AOSP
SettingsState.java caps the delayed XML flush at 2s). `ime set` answers
from memory, so the close-time restore confirmed its read-back and then
`close --shutdown` ran `adb emu kill` immediately; the emulator died
before settings_secure.xml was rewritten and restarted with the test
helper IME as the default keyboard.

The restore now waits out a bounded flush settle after a confirmed
restore, only when the close will kill that emulator, and keeps the
pending recovery marker written through the window. The settle stops
early with the request's abort signal, since a cancelled close never
reaches the kill.
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.13 MB 5.13 MB +1.8 kB
Package (unpacked) 5.13 MB 5.13 MB +1.8 kB
Package (download) 1.54 MB 1.54 MB +654 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 17.9 ms 18.0 ms +0.1 ms
CLI --help 51.2 ms 49.5 ms -1.6 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

Comment thread packages/platform-android/src/ime-restore.ts Outdated
Comment thread packages/platform-android/src/ime-restore.test.ts
@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

CI note: the only red check was the iOS workflow's Smoke Tests preflight step (daemon_startup_failed starting the daemon at 19:44Z). Unrelated to this Android-only diff: the same step failed identically at 19:38Z on #3291's run (fix/windows-daemon-start-3291, different diff), and the lane currently has an open reliability PR (#2491). Re-ran the failed job. The Android smoke job and all other checks (Repo Guards, Typecheck & Package, Integration Tests, Coverage, Compatibility & Provenance) were green on 209600938.

An aborted close resolved the settle immediately, dropped the owned
flag, and cleared the pending recovery marker as if the flush window
had been waited out. The next close of that emulator then took the
no-record fast path and could kill inside the still-open window — the
#3318 failure reached through the ordinary abort-and-retry shape.

The settle deadline is now process-owned state keyed like the recovery
lock: an abort keeps it, and the next shutdown-bound close waits out
the remaining window before returning to the kill. A marker clears
only when the window it fences is fully covered. An aborted settle is
never a completed settle.
@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

P1 + P3 from the cubic review addressed in 10b4126e5 (fix(android): carry an aborted flush settle into the next close).

Defect: an aborted close resolved the settle early but still cleared the pending marker and left the owned flag dropped, so the next close of the same emulator took the no-record fast path and returned to adb emu kill inside the still-open flush window — #3318 via the ordinary abort-and-retry shape.

Fix at the owning state: the flush-settle deadline became process-owned state (pendingTestImeFlushSettles, ime-state.ts, keyed like the recovery lock). An aborted settle keeps deadline + marker; the next shutdown-bound close waits the remaining window before returning to the kill; an elapsed deadline retires without waiting; the marker clears only when the window is fully covered.

New tests (a close cancelled mid-settle): abort-aware sleep mock; abort mid-settle retains marker + deadline; second close consumes the remaining window before returning (sequence restore → abort → second close → kill-safety asserted); elapsed window skips; ordinary close neither consumes nor needs the window.

Validation on 10b4126e5: pnpm typecheck clean; repo-wide pnpm format; pnpm check:affected --run — all runnable checks passed. Not merged, per constraints.

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

1 issue found 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

Prompt for AI agents (unresolved issues)

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


<file name="packages/platform-android/src/ime-restore.ts">

<violation number="1" location="packages/platform-android/src/ime-restore.ts:100">
P1: This branch turns the former no-ownership early return into a recovery-complete `no-record`, so the following code clears the durable marker without inspecting the device. If startup retained a marker for an offline emulator, any later ordinary close can erase it while the helper IME remains active; preserve it unless this call observes the device clean or consumes a deadline from a confirmed restore.</violation>
</file>

Comment thread packages/platform-android/src/ime-restore.ts Outdated
The settle refactor turned the never-activated early return into an
assignment falling through to the shared marker clear, so a close on a
device this process never activated began erasing the durable pending
marker having inspected nothing - severing the offline-retained orphan
from startup recovery and the doctor check.

The root cause was an overloaded reason: no-record meant both "device
inspected, no rebind record" (complete; clear is right) and "never
activated here" (nothing inspected; clear is wrong). Split them. A
not-activated-here close clears only when it consumed a deadline a
confirmed restore opened. Tests pin both directions: a retained marker
survives a nothing-inspected close, and the second-close retry still
clears after consuming the window.
@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

Second P1 (marker-erase regression in the settle refactor) fixed in 6c5221baf, with a complexity-threshold follow-on split in 175fa4d06.

Acknowledged: the trace is right. Converting the never-activated early return into an assignment routed it through the shared marker clear, and isDeviceRecoveryComplete('no-record') === true let an uninspected close erase a retained orphan marker — unreachable on main by control flow alone.

Fix at the overloaded reason, not another boolean: AndroidTestImeRestoreReason gains not-activated-here (nothing inspected; recovery status unknown) distinct from no-record (device inspected, no rebind record). Only the inspected-no-record stays in isDeviceRecoveryComplete; the not-activated-here case clears the marker only when awaitPendingFlushSettle returned consumed — a deadline a confirmed restore opened, which is exactly the second-close retry case that legitimately clears.

Both directions pinned: 'devices this process never activated are left alone, marker and all' now writes the marker first and asserts it SURVIVES (the old test could not observe a clear — that is why the regression shipped); the mid-settle suite pins that the retry-after-abort still consumes its window and clears. Mutation-checked: deleting the ownership check fails 4 tests; widening the clear-earned condition back fails 2.

Validation on 175fa4d06: pnpm typecheck clean; repo-wide pnpm format; pnpm fallow audit --base origin/main clean (the refactor split resolved the complexity finding at the code, no baseline edits); pnpm check:affected --run — all runnable checks passed. Not merged.

@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

Audit pointer for the second P1 (marker-erase regression): my thread reply is discussion_r4224358513 — GitHub collapses resolved threads, so it is easy to miss. Root cause in one paragraph: the flush-settle refactor turned the never-activated early return into an assignment falling through to the shared marker clear, and the reason type overloaded no-record to mean both "inspected, no record" and "never activated here", so an uninspected close erased a retained orphan marker. Fixed at the reason type in 6c5221baf (not-activated-here split out; clears only on settleOutcome === 'consumed'), with 175fa4d06 splitting the sequencing under the complexity threshold. PR body now records Tested commit 175fa4d06, 7 files, and the three review rounds including the two defects they found in shipped code.

@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-android/src/ime-restore.ts Outdated
A retry close that both consumed a leftover deadline and confirmed a
fresh restore slept twice back to back, holding the IME recovery lock
for two flush windows. AOSP SettingsState.doWriteState() rewrites the
whole settings file from the in-memory map, so one flush after the last
write also persists every older pending change: the windows are a
union, not a sum. The settle is now a single max(existing, write +
cap) wait with one outcome for the marker-clear decision.
@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

P3 (doubled flush window, discussion_r4224387869) fixed in 3bbda232d: flush windows coalesce into one max(pending, write + cap) deadline per device — cited to SettingsState.doWriteState() rewriting the whole file from the in-memory map, so one flush after the last write persists the earlier one too. Retry-close lock hold is one window, not two; no window shortened.

The live-emulator evidence the body owed is now recorded: 3/3 cycles of open --relaunch → close --shutdown → AVD restart (-no-snapshot-load) restarted with LatinIME on an arm64 API 35 google_apis emulator; in-session reads showed the helper each time; on-device record and pending markers fully cleared. PR body updated to Tested commit 3bbda232d with the device matrix and the round-four entry in Review history. Gate on that head: pnpm check:affected --run all runnable checks passed. Not merged.

@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

Reply-in-thread audit for anyone reconciling review threads against this PR (all four cubic findings have an author reply; the threads were resolved by cubic's own bot, resolvedBy=cubic-dev-ai[bot], not by me):

Finding My reply
P1 abort-clears-marker discussion_r4224209882
P3 abort-untested mock discussion_r4224210661
P1-2 marker erase via shared clear discussion_r4224358513
P3-2 doubled flush window discussion_r4224488528

Note for tooling: these are review-comment replies (nested in resolved threads); GitHub collapses resolved threads in the default view, and REST /pulls/{n}/comments lists them with in_reply_to_id set — a query that filters to top-level comments will report the thread as having only cubic's comment. PR body updated per coordinator guidance: no per-head 'Tested commit' line (the latest validation comment carries the tested head; currently 3bbda232d, check:affected --run / typecheck / fallow audit clean, CI green), review history now names round four as cubic's double-window finding fixed by coalescing in 3bbda232d.

@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

This PR is ready on the code side, but one piece of evidence is missing. At 3bbda23 all 19 checks pass, and the earlier iOS smoke failure was on a different lane and was re-run. There are no conflicts. Nothing else blocks it.

The live result is 3/3 LatinIME after restart on an arm64 API 35 emulator, and it is author-reported. No run of the base commit on that same AVD is recorded. The issue reproduced on API 32/37 x86_64, so it is not yet shown that the base fails on this emulator. Please add one base-commit cycle on the same AVD: open --relaunch, close --shutdown, restart with -no-snapshot-load, then settings get secure default_input_method. The base should print TestInputMethodService, and the head cycles should print LatinIME. I did not run the emulator cycle or the unit tests myself. I judged the regression coverage by reading the pre-change code. I also could not confirm that 2.5 s covers devices that override SETTINGS_PROVIDER_MAX_WRITE_DELAY_MILLIS, and the PR lists that as an open risk.

Not blocking, and you can take or leave these: ime-restore.ts computes shutdownTarget && device.kind === 'emulator' again at https://github.com/callstack/agent-device/blob/3bbda23/packages/platform-android/src/ime-restore.ts#L108 after lifecycle.ts already computes it, so using the passed boolean would drop the second check. An ordinary close opens no flush deadline at https://github.com/callstack/agent-device/blob/3bbda23/packages/platform-android/src/ime-restore.ts#L104, so a close --shutdown from another session within about 2 s gets 'not-activated-here' and kills at once. Registering the deadline on every confirmed restore, and having only kill-bound closes wait on it, would cover that.

Is there a smaller fix than the timed wait? I looked at two. Waiting for a durable flush signal does not work, because the provider has no shell flush and reading the settings_secure.xml mtime needs root, which Play Store images do not allow. Replacing adb emu kill with adb shell reboot -p might persist settings at shutdown, but that is not verified on all images. The timed wait tied to the AOSP 2 s cap looks like the smallest fix to me, at about 96 net production lines inside platform-android plus one contract field. Would a graceful shutdown be shown to flush SettingsProvider on API 32/37 first, before the kill mechanism changes?

The earlier bot threads are fixed at this head and can be resolved: the aborted-settle marker (#3331 (comment)), the abort-aware sleep mock test (#3331 (comment)), the 'not-activated-here' recovery reason (#3331 (comment)), and the coalesced flush window (#3331 (comment)).

The kill that must miss the flush window can arrive from another
session: an ordinary close confirmed the restore and dropped the
owned flag, and a later close --shutdown on the same device then
inspected nothing and killed at once. Registration now belongs to
the confirmed restore itself, on every close; only kill-bound
closes wait it out. ime-restore also stops re-deriving the
kill-bound predicate from device kind, which lifecycle.ts already
owns; it gates registration to emulators because only emulators
can be killed by the runtime.
@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

Requested base-control run: base does NOT reproduce on this AVD — recording that as a real finding, per the review ask.

Base = 24b2ce638 (pre-fix), same arm64 API 35 google_apis AVD (ReactNative_API_35), same pnpm build + local dist, same -no-snapshot-load restarts:

Step Base run 1 Base run 2 Base run 3
during session TestInputMethodService TestInputMethodService TestInputMethodService
close --shutdown wall time 0.29 s 0.75 s ~0.3 s
after restart LatinIME LatinIME LatinIME

Head (3bbda232d and fcf968b22): in-session helper, close ~2.85–2.9 s (the settle), LatinIME after restart, 5/5 cycles.

What this does and does not prove: the arm64 API 35 run cannot discriminate the fix — the base passes there too, so the earlier "3/3 LatinIME on head" was, as the reviewer suspected, evidence of no-new-breakage on that image, not of the fix. The base's sub-second close confirms the mechanism is present on head's terms (no settle at all on base), but this emulator's SettingsProvider evidently flushes faster than the reporter's. The reproduction's platform scope stands as reported: API 32/37 x86_64 (and per #3318, both google_apis and playstore images). The 2 s-cap risk in the body is unchanged and still the honest open item. I don't have an API 32 or 37 x86_64 AVD provisioned here; x86_64 images under this arm64 host run without HW acceleration, making boot cycles impractical as CI — happy to run one if someone provisions it or has a spare AVD serial.

Falsification control on the hazard itself, same AVD, base dist: ime set to the helper followed by SIGKILL of qemu within ~1 s of the write did strand TestInputMethodService across a restart. That confirms the lost-flush hazard is real on this image too — graceful adb emu kill on this fast emulator simply wins the race on base most of the time, which is exactly the timing-sensitive shape #3318 describes.

Both non-blocking reviewer points addressed in fcf968b22:

  1. restoreAndroidTestIme no longer re-derives shutdownTarget && device.kind === 'emulator' — it consumes the caller's boolean (one predicate owner), and gates registration (not the kill-wait) to emulators because only emulators can be killed by the runtime.
  2. Accepted after tracing the reachability: an ordinary close confirmed the restore and dropped ownership without registering anything; a later close --shutdown from another session read not-activated-here, saw no deadline, and killed inside the flush window. Registration now belongs to every confirmed restore; only kill-bound closes wait. New test 'a kill-bound close after another close's restore waits out the registered window' pins the exact cross-session sequence; 'an ordinary close restores without any flush wait but registers the window' pins registration-without-wait. No user-visible wait lengthened anywhere.

Validation on fcf968b22: pnpm check:affected --run all runnable checks passed; pnpm typecheck clean; pnpm fallow audit --base origin/main clean; new head live cycle: helper in-session, close --shutdown 2.85 s, LatinIME after -no-snapshot-load restart. Not merged.

@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-android/src/ime-restore.ts Outdated
Comment thread packages/platform-android/src/ime-restore.test.ts Outdated
…ry emulator kill on it

Move flush-window registration inside restoreAndroidTestImeFor, the only
function that confirms the ime set landed: no caller — close, cross-session,
or daemon-startup orphan recovery — can restore without registering, and the
startup scan can no longer leave an unguarded window (issue review P1).

Enforce the window where the kill happens instead of at each close caller:
awaitTestImeFlushWindow in the shutdown runtime holds every adb emu kill
(close --shutdown, the standalone shutdown command, and any future caller)
past the newest registered restore, keyed by serial because the window is a
property of the device's SettingsProvider, not of any host state dir.

Unify the recovery-marker clear on one decider shared by the close wrapper
and the startup scan, and switch the settle evidence to monotonic
last-restore timestamps so an aborted wait can never consume evidence the
next kill-bound path still needs.

Co-Authored-By: Apex <noreply@callstack.io>
@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

Validation — head 83e78a7b8

  • pnpm check:affected --run: all runnable checks passed — 448 test files / 3246 tests, fallow audit clean vs origin/main (one flagged unused export fixed at the type by un-exporting the constant, not suppressed).
  • pnpm typecheck: clean.
  • New/changed coverage: kill-site flush gate (shutdown/runtime.test.ts: waits before adb emu kill, skips with no window, abort never reaches adb), startup-orphan registration with no boot sleep (ime-restore.test.ts), registration-by-construction in the inner restore, P3 marker-contributions made separable, all prior #3318 settle tests re-keyed to the serial-timestamp model.

@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

iOS smoke red at fcf968b22 — attributed infra flake; lane re-running at current head

Coordinator triage independently verified, no code changed for it:

Head has since moved to 83e78a7b8 (answers the two open cubic threads — registration at the write + kill-site flush gate), and the coordinator's 23:13Z forward crossed it in flight. The pending Smoke lanes at 83e78a7b8 are byte-identical to fcf968b22 on every iOS path, so they serve as the same-head re-run the triage asked for; pnpm check:affected --run was recorded on 83e78a7b8 separately. If an iOS Smoke lane fails again at 83e78a7b8, the stop-and-report rule applies before any code touch.

@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-android/src/ime-state.ts Outdated
Comment thread packages/platform-android/src/shutdown/runtime.test.ts Outdated
The window guard compared a wall-clock timestamp to a wall-clock
read, so a forward host-clock step (NTP correction, VM resume) between
registration and the kill could make the remaining wait negative and
release the kill inside the provider flush window. Register and derive
the remaining wait from performance.now(), the process monotonic clock
the repo already uses for transport deadlines; the map is process-owned
and never persists, so an absolute epoch time bought nothing.

Pins the kill-site wait magnitude at the full registered budget and
adds a forward wall-clock-jump regression test.

Co-Authored-By: Apex <noreply@callstack.io>
@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

Round seven (cubic) fixed — head 5753cfa45; second iOS red is the preflight daemon-startup class, not a wait-capture repeat

Cubic round seven, both fixed at 5753cfa45 with in-thread replies:

  • P1: the flush window is now timed on performance.now() (process monotonic clock, same one daemon-client-transport.ts deadlines use) for both registration and the kill-site derivation — a forward wall-clock step can no longer shrink the remaining wait. Pinned by a Date.now()-jump regression test.
  • P3: kill-site test asserts the wait magnitude covers the registered budget (>= 2000 ms), not merely positive.

pnpm check:affected --run on 5753cfa45: all runnable checks passed (includes fallow + typecheck per the lane composition used all week).

iOS Smoke, second red this PR — reported per the stop-and-report rule, with the signature shown to be a different class than the first red, so no code was touched for it:

@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-android/src/shutdown/runtime.test.ts Outdated
…k name

The coordinator's confirmation of cubic's P1 adds the shape the code
should carry: the map is now testImeLastRestoreAtPerfMs, so 'AtMs' no
longer misreads as a wall-clock epoch, and the comment states the
two-clock mechanism (sleep is timed on libuv's monotonic loop clock; a
wall-clock-derived remaining time mixes clocks by construction — the
skew stable-capture.ts documents). One monotonic source for
registration and expiry, no clamp hiding anything.

P3 extension: the abort test now pins the NUMBER the design rests on —
the mark is seeded 1000 ms old so the first wait must derive ~1500, and
after the aborted kill the retry re-derives the same remainder from the
surviving mark. A constant sleep, or an abort that consumed the
evidence, fails on the number.

Co-Authored-By: Apex <noreply@callstack.io>
…window

awaitTestImeFlushWindow read the mark once, slept, and reported
'covered' even when a newer restore had registered while it slept —
releasing the kill inside the extension's window, which is #3318
again. It now re-reads the mark after every sleep and loops until the
newest deadline passes, consuming only the mark each round waited for.

The kill tests observe the middle of the wait with a deferred sleep:
adb must not run while the wait is pending, and a restore landing
mid-wait must produce a second, extension-sized sleep before the kill.

Co-Authored-By: Apex <noreply@callstack.io>
@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Validation — head 91d4c0b0a

Round nine (cubic P1 + P2, both valid, both fixed):

  • P1 (production fix): awaitTestImeFlushWindow now re-reads the mark after every sleep and loops until the newest deadline passes — a restore landing mid-wait extends the pending kill instead of the caller reporting covered on a stale mark and killing inside the extension's window. Reply: discussion_r4225426654.
  • P2 (test fix): the happy-path hold test observes the middle of the wait with a deferred sleep (run not called while pending, then the kill proceeds). Same conversion exposes and fixes a mockClear→mockReset leak of queued mockImplementationOnce between tests. Reply: discussion_r4225426807.
  • New extension test is mutation-checked: fails on the previous single-read implementation with sleep called 1 time, expected 2.

pnpm check:affected --run on 91d4c0b0a: all runnable checks passed (fallow + typecheck included).

The window is process-monotonic and in-memory by construction, so it
cannot survive a daemon restart; every restore shares that boundary,
not just startup recovery. Recording why the restart-crossing fix was
not taken here: a cross-process deadline must be wall-clock or
boot-time based, reopening the clock-skew class the monotonic clock
exists to exclude, and a state-dir file host through the
shutdown-runtime contract is a boundary change for a human reviewer
to decide.

Co-Authored-By: Apex <noreply@callstack.io>
@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Validation — head 7c8297593

Round ten (cubic P1 at ime-restore.ts:160): mechanism confirmed, classified as a design boundary (cross-process flush deadlines), not fixed in this PR. Reply with the full reasoning: discussion_r4225475624. The head itself is comment-only: the map's doc comment now records the restart boundary, why the cross-process fix was not taken (wall-clock conflict with round seven + contracts change), and the cheapest alternative for a human reviewer.

pnpm check:affected --run on 7c8297593: all runnable checks passed (fallow + typecheck included). No behavior change in this head.

@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-android/src/ime-state.ts Outdated
Comment thread packages/platform-android/src/ime-restore.test.ts Outdated
…3346

The coordinator endorsed closing the cross-restart durability finding
as a filed follow-up rather than expanding this PR's scope. The map
comment now names the issue so the boundary and its owning work sit
beside each other.

Co-Authored-By: Apex <noreply@callstack.io>
@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Validation — head ce40232e3

Coordinator option (b) executed for the restart-durability finding: follow-up issue #3346 filed (implementation contract: required behavior, the monotonic-vs-persist clock constraint, the mandatory expiry rule, completion tests, dependency on this PR's mechanism), the thread reply states the clock choice plainly (discussion_r4225521206), and the ime-state.ts boundary comment now names #3346 (ce40232e3, comment-only head).

  • The coordinator's two 00:08Z items were already closed before this note: mid-wait covered race fixed at 91d4c0b0a (re-read loop + mutation-checked extension test), deferred-sleep interleaving shipped there, lock-availability verdict posted at discussion_r4225489974. All review threads resolved; nothing silently outstanding.

pnpm check:affected --run on ce40232e3: all runnable checks passed (fallow + typecheck included). No behavior change in this head.

thymikee and others added 3 commits October 9, 2026 02:44
The flush mark registers only after the shell call returns and the
readback confirms, but the provider's window opens when the device
accepts the write. A kill-bound wait reading the map during that gap
saw 'idle' and fired adb mid-write. The registrar now opens a pending
entry before issuing the write and closes it in a finally that lands
after registration, and awaitTestImeFlushWindow drains in-flight
writes at the top of every round before consulting marks — including
a write that opens during its sleep.

Also derives the flush-window tests' seeded elapsed values and sleep
bounds from the now-exported SETTINGS_PROVIDER_FLUSH_SETTLE_MS so a
changed window shifts their meaning instead of turning them red for
a copied-number reason.

Co-Authored-By: Apex <noreply@callstack.io>
…he race test

Drop a useless spread; split awaitTestImeFlushWindow into drain,
round, and consume helpers under the complexity threshold without
changing its rounds' semantics. The in-flight test now drains a real
event-loop turn before asserting, so it fails when the drain guard is
removed (verified) instead of racing the kill's first microtask.

Co-Authored-By: Apex <noreply@callstack.io>
The two remaining 500 ms margins become 0.8 * the window so every
flush-window bound in both suites moves with the constant.

Co-Authored-By: Apex <noreply@callstack.io>
@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Validation — heads 4d1b6d094 / 248aa0b35 / b0516feb8

Round eleven (cubic, both valid, both fixed):

  • P1 (production fix, 4d1b6d094): in-flight ime set writes are now tracked from ISSUE time (pending set + registrar finally landing after registration), and the kill-site wait drains them each round before consulting marks — closing the gap where a racing kill read idle between write acceptance and mark registration. Threads replied: discussion_r4225609315.
  • P3 (test hygiene, 4d1b6d094+b0516feb8): SETTINGS_PROVIDER_FLUSH_SETTLE_MS exported; every seed and sleep band in both flush-window suites derives from it proportionally.

248aa0b35 carries the lint/fallow repairs (spread removal; wait split into drain/round/consume helpers under the complexity threshold, semantics unchanged) plus the in-flight test's event-loop-turn hardening.

pnpm check:affected --run on b0516feb8: all runnable checks passed (lint + fallow + typecheck included). Recorded for 4d1b6d094 and 248aa0b35 in-thread; this comment is the current-head record.

@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Coordinator round twelve processed against the current head: both findings were already fixed and replied (4d1b6d094/248aa0b35/b0516feb8), and the reachability proof they asked for is now posted with a self-correction — PROVEN interleaving is startup recovery (fire-and-forget at daemon-runtime.ts:700) vs an early kill; two-session closes are a gated candidate; the standalone-shutdown refutation holds only for session-named shutdowns. Discussion: discussion_r4225645334 + the correction reply. No new code warranted; gates stand at b0516feb8 (recorded). CI 0 fail.

@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Coordinator note processed; state checked against the live board at 2026-10-09T01:02:58Z. Trailer correction accepted and adopted: future board claims will carry the checked timestamp rather than an unsourced "all resolved" (the 00:31:41Z pair was live when my 00:34:51Z comment went out — whether from check staleness or my own lag, a timestamp makes the claim auditable either way).

iOS Smoke red at ce40232e3: re-run triggered for run 37865442839 (failed jobs only) — the coordinator's explicit instruction, executed even though the head is superseded by b0516feb8. Attribution accepted exactly as framed, with the class kept separate: this is the #2491 xcrun --show-sdk-version probe-timeout class (syspolicyd first-invocation stall on fresh runners, matching closed-but-recurring #2422), not the #3342 daemon_startup_failed class — no new issue filed, no fold. Out-of-bounds list acknowledged verbatim: no raising the 15 s budget, no retry loop around the probe, no CI special-casing. The b0516feb8 re-run (triggered last turn) is also green: 3 of its 4 Smoke lanes already pass, 1 in flight.

The two cubic threads from 00:31:41Z: both closed and replied before this note (heads 4d1b6d094/248aa0b35/b0516feb8; board confirms zero unresolved threads at the timestamp above):

  • P1 (ime-state.ts:82, registration-timing race) — fixed at 4d1b6d094 by the coordinator's second suggested shape (track the pending write from issue; rejected register-before-issue because it owes 2.5 s to writes that never reached the device, and rejected serialization for the lock-key mismatch established round ten). The reachability proof was posted in-thread (discussion_r4225645334) and then self-corrected (discussion_r4225652797) under this round's own rule: PROVEN interleaving is startup orphan recovery (fire-and-forget void recoverStartupResources at src/daemon/server/daemon-runtime.ts:700, un-awaited before openDaemonServers()) vs an early kill-bound close; two-session concurrent closes are a gated CANDIDATE (require claim takeover); session-named standalone shutdown is REFUTED for the whole teardown (retire at session-close.ts:345 post-teardown keeps DEVICE_IN_USE true); selector-style standalone shutdown is honestly labeled OPEN (the guard at session-state.ts:334 only fires for session-named requests; the bind/claim barrier beyond it was not fully audited). The shipped fix sits on the single registrar keyed by serial, so all four rows of that ledger are covered uniformly.
  • P3 (hardcoded window) — resolved at 4d1b6d094+b0516feb8. Chosen option, stated for the record: the @internal-convention export of SETTINGS_PROVIDER_FLUSH_SETTLE_MS beside the existing test-exported map (the coordinator's smaller of the two options), with every literal replaced by proportional seeds/bands in both suites.

Board at 01:02:58Z: threads 0 open; CI 0 fail, 16 pass, 2 pending (the Smoke re-runs), 1 skipping; mergeable at b0516feb8, gates all-pass (recorded at issuecomment-6072041297). No code changes this pass, so no new head and no new gate record. #3346 remains the home of the restart-durability residual.

… the outcome

The pending-write mechanism treats ISSUE as the moment the provider's
window may start, but registration sat on the confirmed path only: a
set-failed readback mismatch closed the pending entry with no mark,
letting a drained kill-bound waiter read 'idle' and fire adb inside
the window a half-accepted write may have opened. The mark now
registers in the registrar's finally, beside the pending-close, for
every issued emulator restore; registerTestImeRestore's max keeps
that purely lengthening. The set-failed branch stays purely about
recovery evidence: retained record and marker still cover retry.

Co-Authored-By: Apex <noreply@callstack.io>
@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Validation — head b79910d46 (coordinator round-thirteen items)

Gate failures at 4d1b6d094: confirmed real, confirmed repaired at 248aa0b35 (before this note arrived — the note was drafted two heads back). The two gates that actually red, run per instruction at this head:

$ pnpm lint            # oxlint . --deny-warnings
(no output; exit 0 — oxlint prints findings only)

$ pnpm run check:fallow --base 24b2ce638d0f6fd763b4cd44a303c533573cf519
Audit scope: 9 changed files vs 24b2ce638d (b0516feb8..HEAD)
✓ No issues in 9 changed files

Both green on GitHub's runners too at b0516feb8 (Lint & Format pass 30s, Compatibility & Provenance pass 44s). No baseline edits, no ignore comments: the complexity finding was resolved by the split the coordinator independently prescribed — quietTestImeRestoreWrites owns the drain-or-abort verdict, runTestImeFlushRound one round, consumeTestImeFlushMark the mark verdict — and awaitTestImeFlushWindow is now a four-line loop. "We never consult marks while a write is in flight" is a callable claim inside one function, as asked. restoreAndroidTestImeFor untouched per instruction.

The set-failed position — decided deliberately, option (a), one notch stronger: the code encoded "readback mismatch ⇒ no provider write" silently; the premise that justified pending-from-issue rejects that claim, and rejects it for more than the readback branch — a thrown transport failure after dispatch carries the same half-accepted ambiguity. So the mark now registers in the registrar's finally, for every issued emulator restore, exactly beside the pending-close and always before it: a draining waiter cannot observe closed-but-unregistered, and registerTestImeRestore's max makes late registration purely lengthening. One rule, one site; the set-failed branch comment now says it stays purely about recovery evidence (retained record + marker still earn the retry path; the hold covers the possible half-flush). Evidence: git show b79910d46:packages/platform-android/src/ime-restore.ts.

Test truth changed with the code, not around it: a restore that did not switch the IME back skips the flush wait became …still owes the flush wait it may have written (asserts the full-window sleep AND the retained marker/record in the same test), and the kill-site release test is retitled to the surviving production shape — a physical-device restore opens the pending entry without ever registering (ime-state.ts gate is device.kind === 'emulator'), which is what keeps the drain-with-no-mark path exercised for real.

pnpm check:affected --run on b79910d46: all runnable checks passed. iOS Smoke stays out of scope per instruction (re-runs only; #2491 class). Nothing merged.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 9, 2026
@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

The code in b79910d now fixes what the earlier review of 3bbda23 left open: close --shutdown restores the IME outside the settings flush window, and the wait drains pending writes first. I found no new code problems. I read the code path only. I did not run the unit tests or pnpm check:affected, and I did not reproduce the device runs the author reports.

Not blocking: the comments in packages/platform-android/src/ime-state.ts and the tests carry review history (round numbers, a reviewer tag), and so do the long workaround paragraphs in ime-restore.ts, so please keep only the invariant sentence in each, since AGENTS.md asks to keep review history out of comments. Also, the test at ime-restore.test.ts:382 is said to pin marker and record retention on set-failed, but it never seeds or asserts either, so please seed the marker and assert it, or correct the claim. You can take or leave both.

CI is green, with 18 of 18 checks passing at b79910d, and the author reports lint and fallow green at this head. I know of no conflicts.

Of the inline threads resolved since the earlier review, one still applies: the in-memory-only window thread (#3331 (comment)). I accept it as non-blocking, because #3346 tracks it. The fixes for the others landed at this head: #3331 (comment) (startup recovery now registers correctly), #3331 (comment) (cross-session test order is fixed), #3331 (comment) (registration and expiry use one clock), #3331 (comment) (test floor no longer derived from the constant), #3331 (comment) (AbortError reason assertion is fixed), #3331 (comment) (the round loop extends on newer marks, covered at runtime.test.ts:172), #3331 (comment) (deferred sleep and no mid-wait run are asserted), #3331 (comment) (the write is registered before ime set, covered at runtime.test.ts:115), and #3331 (comment) (test bounds now derive from the settle constant).

Evidence has limits. No reproduction separates base from head, because the base also restores LatinIME on the author's arm64 API 35 AVD, and the reported API 32 and 37 x86_64 images were not run. What stands in for it is the author's report of a SIGKILL-within-1s hazard control on the same image, plus head cycles with a 2.85 s close and LatinIME after restart. I could not confirm that 2.5 s covers vendor images that override SETTINGS_PROVIDER_MAX_WRITE_DELAY_MILLIS, which the PR names as an open risk.

Nothing more is needed in code before merge. A maintainer should either accept the hazard-control evidence or ask for a base-versus-head close --shutdown cycle on an API 32 or 37 x86_64 image.

…in retention in the set-failed test

AGENTS.md keeps control-flow narration and review history out of
implementation comments; each site now carries one invariant sentence.
The set-failed flush-wait test seeds the durable marker and asserts
record retention itself instead of crediting other tests for what it
does not prove.

Co-Authored-By: Apex <noreply@callstack.io>
@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Both cleanups done at 5d679cad4, and your closing question answered below.

Review history in code: round numbers, reviewer tags, and the "Invariants the review requires" framing are gone from ime-state.ts, ime-restore.ts, and both test files — each site keeps its invariant sentence (a grep for round [0-9]|cubic|reviewer|coordinator across the five files is empty). The restart-boundary paragraph in the map comment is now four lines pointing at #3346 rather than narrating the decision trail.

The overclaiming test: fixed by seeding rather than softening, since the sentence described a real invariant worth pinning here. a restore that did not switch the IME back still owes the flush wait… now writes the marker before the close and asserts, in the same test: the full-window sleep, the retired in-memory entry, markerStore still holding the device, and the persisted record still present. Both mechanisms pinning together is exactly this branch's contract.

Evidence limits — what ran where, in one place:

Run on the arm64 API 35 AVD (ReactNative_API_35, the only emulator provisioned on this host):

  • Head cycles: five consecutive close --shutdown runs after test-IME activation at head — each close held ~2.85 s before adb emu kill, and LatinIME (the user's prior keyboard) was the default after each cold restart. 5/5.
  • Base control: the same activation/close sequence on base 24b2ce638 did not reproduce the stranding — LatinIME came back every time. That is a falsification result, not a pass for base: it means this specific image does not manifest the bug at the timing the close naturally produces (its flush completes fast enough that the unheld kill still lands after it).
  • Hazard control (SIGKILL): on the same base image, killing the emulator process within 1 s of a confirmed ime set did strand the test IME across restart — proving the provider's delayed flush is real on this AVD and that the window the fix holds is the window that matters. So the discriminating pair on this host is hazard-control-red vs head-green; base-vs-head on natural timing is indistinguishable here by construction.

Not run: any base-versus-head close --shutdown comparison on API 32 or API 37 x86_64 images. This host is arm64 and cannot hardware-accelerate x86_64 AVDs, so those images were never provisioned; the reporter's platforms remain the place where base-vs-head could still diverge, which is why it is in the body as the named open item rather than reported as done. If a maintainer weights that discriminator, a head-vs-base cycle pair on any API 32/37 x86_64 (including a software-rendered cloud runner) is a ~10-minute experiment; my claim is only that on arm64/API-35 the natural-timing comparison cannot discriminate, and the hazard control is the substitute evidence that the mechanism bites.

The SETTINGS_PROVIDER_MAX_WRITE_DELAY_MILLIS vendor-override coverage and the restart-durability residue stay where you left them (Unresolved risk line; #3346). ready-for-human remains applied; validation for 5d679cad4: pnpm check:affected --run all runnable checks passed (includes the two Android suites with the re-pinned test), pre-push.

@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

Comment thread packages/platform-android/src/ime-restore.ts Outdated
Comment thread packages/platform-android/src/ime-state.ts Outdated
…earn marker clears

The mark map carried a timestamp alone, so the marker-clear rule could only
infer that a covered window proved recovery. Registering every issued write
from the finally made that inference unsound: a set-failed restore's mark,
covered by a later not-activated-here kill-bound close, let that close clear
the durable recovery marker while the helper was still active. Marks now
carry the write's own provenance (atPerfMs + confirmed), the flush wait
reports covered-issued vs covered-confirmed, and the clear rule consumes the
fact. Registration stays in the finally for every issued emulator write: the
hold is owed regardless; only confirmation retires retry evidence.

@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

Comment thread packages/platform-android/src/shutdown/runtime.test.ts Outdated
Comment thread packages/platform-android/src/ime-restore.test.ts Outdated
@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Verification record for 663f11d8c (provenance-typed restore marks; answers both 03:08:50Z threads):

$ pnpm check:affected --run
 Test Files  448 passed (448)
      Tests  3251 passed (3251)
check:affected: all runnable checks passed.

pnpm lint exit 0; pnpm format applied repository-wide; history grep (round [0-9]|cubic|reviewer|coordinator) empty across all five touched files.

…sleep

seedRestoreMark lived byte-identical in two test files, so the last mark-shape
change had to land twice; it now lives in the IME fixtures module beside the
fakes it serves, one edit per future shape change. The marker-retention test's
sleep assertion promised the FULL flush hold but accepted any positive value;
it now carries the file's own nearly-full-window bound, derived from the
window constant.
@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Verification record for b36626a4f (shared mark seeder + bounded owed-hold sleep; answers the 03:31:57Z pair):

$ pnpm check:affected --run
 Test Files  449 passed (449)
      Tests  3253 passed (3253)
check:affected: all runnable checks passed.

pnpm lint exit 0; pnpm format applied repository-wide; history grep (round [0-9]|cubic|reviewer|coordinator) empty across the touched files. No production code changed in this commit — fixtures and test assertions only.

@thymikee

thymikee commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

This PR is ready. I re-reviewed it at b36626a. The code now fixes what the earlier review at b79910d (#3331 (comment)) raised, and I have no remaining code findings. CI is green, with 19 of 19 checks passing at b36626a, and there are no conflicts.

Not blocking, and you can take or leave these: registerTestImeRestore in ime-state.ts:120 replaces the stored provenance instead of inheriting it, so no test would catch a regression of the rule that a later unconfirmed write after a confirmed one must yield covered-issued (a case in ime-restore.test.ts through restoreAndroidTestIme would cover it: a confirmed close, then a set-failed close, then a not-activated-here shutdownTarget close, asserting the marker is retained); and the comment in runtime.test.ts:113 still says the mark exists only after "the readback confirms it", but since b79910d the finally registers it for every issued emulator write.

Of the open threads, these are fixed at this commit and can be resolved: #3331 (comment) (covered-confirmed required at ime-restore.ts:59), #3331 (comment) (comment in ime-state.ts:51-59 corrected), #3331 (comment) (seedRestoreMark now shared), and #3331 (comment) (unconfirmed-window test now asserts the settle floor). No open thread still applies.

On evidence, I did not run the unit tests or pnpm check:affected, and I judged the regression test's validity from the code at b79910d. The 5/5 live LatinIME cycles on arm64 API 35 were recorded at 3bbda23 and fcf968b, not at b36626a. The newest change leaves kill timing alone on the confirmed path and only alters how the marker is cleared, which the in-process tests cover. The base commit does not reproduce the bug on the author's AVD, so no device run separates base from head. I could not confirm that the 2.5 s window covers vendor images that override SETTINGS_PROVIDER_MAX_WRITE_DELAY_MILLIS. Before merge, the maintainer should either accept the API 35 evidence or ask for a base-versus-head close --shutdown cycle on an API 32 or 37 x86_64 image.

@thymikee
thymikee merged commit b9072ba into main Oct 9, 2026
19 checks passed
@thymikee
thymikee deleted the fix/android-shutdown-ime-flush-window branch October 9, 2026 06:53
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-09 06:53 UTC

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.

Android: after close --shutdown, the emulator restarts with the test IME as its default keyboard

1 participant