Skip to content

fix(apple-runner): stop an inline status probe clearing an outstanding command charge - #2994

Merged
thymikee merged 1 commit into
mainfrom
t3code/fix-2965-adversarial-review-pr
Sep 27, 2026
Merged

thymikee merged 1 commit into
mainfrom
t3code/fix-2965-adversarial-review-pr

Conversation

@thymikee

@thymikee thymikee commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Summary

The runner answers the status and uptime readiness probes inline — off its journal and off its serial command queue — so a probe reply is no evidence about queued work. The session charged every request alike, so a probe's answer could discharge a mutation this process had already given up on, and graceful shutdown handed a runner to the next daemon mid-mutation.

Only queued commands are charged now. A charge is released by that command's own answer or its own terminal journal evidence; a queued answer forgives at most one abandoned charge sent before it. Per review, the ledger lives in runner-session-types.ts beside resolveRunnerDetachDecision — its only reader, and already in the Apple eager closure — so the PR adds no static edge and the route/lane machinery is gone. A JSON body that is not an object is now transport-shaped rather than an empty runner envelope, so it cannot settle a charge.

17 files: 5 production (+270/−96).

Validation

At 6902f103e: pnpm check:affected --run green (394 files, 2,766 tests); check:fallow --base origin/main clean; eager-closure-budgets.test.ts green at 691 tests; check:production-exports unchanged at 68. Handoff rows are derived from the runner journal's own RunnerCommandLifecycleState declaration, and the daemon's readinessProbe trait is pinned to the runner's inlineResponse(for:) arms. Named mutations each turn a test red: charging probes, forgiving later residue, terminal evidence consuming an awaited or a stranger's charge, markAbandoned guessing an id.

Unresolved risk: handoff refusal is exercised through the fake-runner harness; no device lane asserts it.

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.85 MB 4.85 MB +1.4 kB
Package (unpacked) 4.85 MB 4.85 MB +1.4 kB
Package (download) 1.45 MB 1.45 MB +446 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.1 ms 27.6 ms +0.5 ms
CLI --help 79.7 ms 79.3 ms -0.4 ms

@cubic-dev-ai cubic-dev-ai 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.

3 issues found across 16 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/platform-apple/src/runner/runner-command-accounting.ts">

<violation number="1" location="packages/platform-apple/src/runner/runner-command-accounting.ts:112">
P2: `settleAnswered` forgives the oldest abandoned residue in the lane without requiring it to predate the exchange being answered. If commands run concurrently on one session and a newer exchange (cmd-b) drops its transport while an older exchange (cmd-a) is still in flight, cmd-a's later answer discharges cmd-b's abandoned charge even though the runner's serial queue places cmd-b after cmd-a — so cmd-a's answer proves nothing about cmd-b. That is the same wrong-discharge class this PR eliminates for probe traffic, and it can clear a charge for a command the runner may still be executing, allowing the handoff mid-mutation. The handful of tests pin only the in-order case (answered exchange newer than the forgiven residue); the out-of-order case is unpinned.</violation>

<violation number="2" location="packages/platform-apple/src/runner/runner-command-accounting.ts:128">
P1: Malformed response envelopes can discharge an outstanding command here. `parseRunnerResponse` turns valid non-object JSON such as `null` into `{}`, and `buildRunnerResponseError` attaches it as `details.runner`. This branch therefore treats an unstructured response as answered and may permit handoff while queued work still executes; require a validated runner envelope before settling.</violation>
</file>

<file name="packages/platform-apple/src/runner/runner-command-recovery.ts">

<violation number="1" location="packages/platform-apple/src/runner/runner-command-recovery.ts:196">
P3: `RUNNER_TERMINAL_LIFECYCLE_STATES` and `handleRunnerCommandStatusRecovery` encode the same lifecycle vocabulary as two separate literal lists ('completed'/'failed' in the set, plus 'accepted'/'started' in the handler's branches). Only the new set is pinned to the Swift journal declaration; the handler's literals are not derived from it, so a state change on either side (or a new terminal state) can make settlement and the invalidation/recovery verdict diverge silently. Pull the handler's laborious branches off the same documented set, or at least key both off one exported constant.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

route: RunnerCommandChargeRoute,
error: unknown,
): void {
if (isStructuredRunnerFailure(error)) this.settleAnswered(commandId, route);

@cubic-dev-ai cubic-dev-ai Bot Sep 26, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Malformed response envelopes can discharge an outstanding command here. parseRunnerResponse turns valid non-object JSON such as null into {}, and buildRunnerResponseError attaches it as details.runner. This branch therefore treats an unstructured response as answered and may permit handoff while queued work still executes; require a validated runner envelope before settling.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/platform-apple/src/runner/runner-command-accounting.ts, line 128:

<comment>Malformed response envelopes can discharge an outstanding command here. `parseRunnerResponse` turns valid non-object JSON such as `null` into `{}`, and `buildRunnerResponseError` attaches it as `details.runner`. This branch therefore treats an unstructured response as answered and may permit handoff while queued work still executes; require a validated runner envelope before settling.</comment>

<file context>
@@ -0,0 +1,195 @@
+    route: RunnerCommandChargeRoute,
+    error: unknown,
+  ): void {
+    if (isStructuredRunnerFailure(error)) this.settleAnswered(commandId, route);
+    else this.markAbandoned(commandId, route);
+  }
</file context>
Fix with cubic

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.

Valid and fixed at d420419fb. The root cause was the shared decoder, not this branch: decodeRunnerResponseBody returned {} for any non-object JSON and buildRunnerResponseError attached it as details.runner, so isStructuredRunnerFailure read a proxy page or a half-written body as an answer. It now throws COMMAND_FAILED for JSON scalars and arrays via isRunnerEnvelopeObject, so all three readers agree a non-object body answered nothing and the ledger only sees a real envelope. Pinned in runner-response.test.ts: null, 42, "ok" and [] throw, and a null body is asserted transport-shaped rather than structured. Restoring the old ternary turns three of those tests red.

Comment thread packages/platform-apple/src/runner/__tests__/runner-swift-settlement-fixtures.ts Outdated
Comment on lines +112 to +120
settleAnswered(commandId: string | undefined, route: RunnerCommandChargeRoute): void {
const answered = this.takeCharge(commandId, route);
if (!answered || answered.abandoned) return;
const residue = this.charges.find((charge) => charge.abandoned && charge.route === route);
if (residue) this.charges.splice(this.charges.indexOf(residue), 1);
}

/**
* Settles the charge for an exchange that ended outside the success path. A structured runner reply

@cubic-dev-ai cubic-dev-ai Bot Sep 26, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: settleAnswered forgives the oldest abandoned residue in the lane without requiring it to predate the exchange being answered. If commands run concurrently on one session and a newer exchange (cmd-b) drops its transport while an older exchange (cmd-a) is still in flight, cmd-a's later answer discharges cmd-b's abandoned charge even though the runner's serial queue places cmd-b after cmd-a — so cmd-a's answer proves nothing about cmd-b. That is the same wrong-discharge class this PR eliminates for probe traffic, and it can clear a charge for a command the runner may still be executing, allowing the handoff mid-mutation. The handful of tests pin only the in-order case (answered exchange newer than the forgiven residue); the out-of-order case is unpinned.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/platform-apple/src/runner/runner-command-accounting.ts, line 112:

<comment>`settleAnswered` forgives the oldest abandoned residue in the lane without requiring it to predate the exchange being answered. If commands run concurrently on one session and a newer exchange (cmd-b) drops its transport while an older exchange (cmd-a) is still in flight, cmd-a's later answer discharges cmd-b's abandoned charge even though the runner's serial queue places cmd-b after cmd-a — so cmd-a's answer proves nothing about cmd-b. That is the same wrong-discharge class this PR eliminates for probe traffic, and it can clear a charge for a command the runner may still be executing, allowing the handoff mid-mutation. The handful of tests pin only the in-order case (answered exchange newer than the forgiven residue); the out-of-order case is unpinned.</comment>

<file context>
@@ -0,0 +1,195 @@
+   * the serial queue, so it forgives no queued residue, and the command it probed stays charged until
+   * its own answer or its own terminal evidence lands (#2965).
+   */
+  settleAnswered(commandId: string | undefined, route: RunnerCommandChargeRoute): void {
+    const answered = this.takeCharge(commandId, route);
+    if (!answered || answered.abandoned) return;
</file context>
Suggested change
settleAnswered(commandId: string | undefined, route: RunnerCommandChargeRoute): void {
const answered = this.takeCharge(commandId, route);
if (!answered || answered.abandoned) return;
const residue = this.charges.find((charge) => charge.abandoned && charge.route === route);
if (residue) this.charges.splice(this.charges.indexOf(residue), 1);
}
/**
* Settles the charge for an exchange that ended outside the success path. A structured runner reply
settleAnswered(commandId: string | undefined, route: RunnerCommandChargeRoute): void {
const answeredIndex = this.findChargeIndex(commandId, route);
if (answeredIndex === -1) return;
const [answered] = this.charges.splice(answeredIndex, 1);
if (answered.abandoned) return;
const residueIndex = this.charges.findIndex(
(charge, index) => charge.abandoned && charge.route === route && index < answeredIndex,
);
if (residueIndex !== -1) this.charges.splice(residueIndex, 1);
}
Fix with cubic

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.

Valid, and the wrong-discharge it describes is real. Fixed at d420419fb: settleAnswered records charges in send order and forgives only a residue that predates the answer — index < answeredIndex && charge.abandoned — so cmd-a's answer can no longer clear cmd-b's abandoned charge even though both sit in one ledger. runner-session-types.test.ts's "does not forgive an abandoned charge sent after it" pins exactly your cmd-a/cmd-b interleaving; dropping the index < answeredIndex guard turns it red, which I verified. An answer landing on a charge already marked abandoned is treated as that exchange's own late reply and forgives nothing further.

Comment thread packages/platform-apple/src/runner/runner-session.ts Outdated
* `started` are written as execution opens, and `notAccepted` is what `status` reports for an id the
* journal does not hold — none of them says the command finished.
*/
const RUNNER_TERMINAL_LIFECYCLE_STATES: ReadonlySet<string> = new Set(['completed', 'failed']);

@cubic-dev-ai cubic-dev-ai Bot Sep 26, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: RUNNER_TERMINAL_LIFECYCLE_STATES and handleRunnerCommandStatusRecovery encode the same lifecycle vocabulary as two separate literal lists ('completed'/'failed' in the set, plus 'accepted'/'started' in the handler's branches). Only the new set is pinned to the Swift journal declaration; the handler's literals are not derived from it, so a state change on either side (or a new terminal state) can make settlement and the invalidation/recovery verdict diverge silently. Pull the handler's laborious branches off the same documented set, or at least key both off one exported constant.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/platform-apple/src/runner/runner-command-recovery.ts, line 196:

<comment>`RUNNER_TERMINAL_LIFECYCLE_STATES` and `handleRunnerCommandStatusRecovery` encode the same lifecycle vocabulary as two separate literal lists ('completed'/'failed' in the set, plus 'accepted'/'started' in the handler's branches). Only the new set is pinned to the Swift journal declaration; the handler's literals are not derived from it, so a state change on either side (or a new terminal state) can make settlement and the invalidation/recovery verdict diverge silently. Pull the handler's laborious branches off the same documented set, or at least key both off one exported constant.</comment>

<file context>
@@ -179,6 +185,40 @@ async function tryRecoverRunnerCommandAfterTransportError(
+ * `started` are written as execution opens, and `notAccepted` is what `status` reports for an id the
+ * journal does not hold — none of them says the command finished.
+ */
+const RUNNER_TERMINAL_LIFECYCLE_STATES: ReadonlySet<string> = new Set(['completed', 'failed']);
+
+/**
</file context>
Fix with cubic

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.

Fixed at d420419fb, at the shared vocabulary rather than the two sites. runner-command-recovery.ts now declares RUNNER_TERMINAL_LIFECYCLE_STATES and RUNNER_IN_FLIGHT_LIFECYCLE_STATES side by side, and handleRunnerCommandStatusRecovery routes accepted/started through the in-flight set instead of its own literals, so settlement and the invalidation verdict read one declaration. completed and failed keep separate branches only because their verdicts genuinely differ (a completed entry replays its payload, a failed one reports the runner's error). A state the runner adds cannot slip through silently: both sets and the recovery rows are pinned to Swift's own RunnerCommandLifecycleState by requireLifecycleSettlementRows, which asserts in both directions, so an unread state fails as a missing row.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at 9fb94ee. The fix stops a queued reply from clearing an inline probe's charge, but the new runner-command-accounting.ts module puts a fresh static edge into the eager-closure of every Apple façade that reaches runner-session.ts, and CI Coverage is failing on that: scripts/tests/eager-closure-budgets.test.ts names the exact route this PR adds at https://github.com/callstack/agent-device/blob/9fb94ee/packages/platform-apple/src/runner/runner-session.ts#L48 and https://github.com/callstack/agent-device/blob/9fb94ee/packages/platform-apple/src/runner/runner-adoption.ts#L12, so this looks caused by the diff rather than a flake.

Could the ledger live in runner-session-types.ts next to resolveRunnerDetachDecision instead, since that module is already in the closure and is the ledger's only reader, and could inline (readiness-probe) exchanges go uncharged altogether the way the preflight uptime already is, so the route field, the blank-id lane, and the inline-residue forgiveness in https://github.com/callstack/agent-device/blob/9fb94ee/packages/platform-apple/src/runner/runner-command-accounting.ts#L115 simply disappear and an inline answer settles nothing?

I did not run the tests or the author's mutation of the extraction-commit code; the regression read comes from tracing the pre-change arithmetic by hand. Whether the abandoned-inline-probe scenario is reachable in practice depends on how a request-cancelled uptime or prewarm skips session invalidation, and I did not confirm how often that path is hit; the retry and cancel paths I checked do invalidate the session. The lost-response case was exercised only through the fake-runner harness via runAppleRunnerCommand, not on a live device; device lanes passed but don't assert this handoff. The concurrency and null-envelope questions raised elsewhere both sit on predicates this PR didn't introduce, and I haven't verified whether one runner session can see concurrent executeRunnerCommandWithSession calls.

Before this can merge, the eager-closure-budgets check needs to go green, which means moving the ledger into a module the closure already evaluates and rerunning Coverage.

Not blocking: the accounting module's comments read like issue history rather than invariants and one test comment contradicts its own assertion, RUNNER_TERMINAL_LIFECYCLE_STATES duplicates the completed/failed branching already in runner-command-recovery.ts, and the Swift fixture walker in runner-swift-settlement-fixtures.ts skips some multi-line case-arm shapes even though today's fixtures still parse correctly — all take-it-or-leave-it.

@thymikee
thymikee force-pushed the t3code/fix-2965-adversarial-review-pr branch from 9fb94ee to d420419 Compare September 26, 2026 15:17
@thymikee

Copy link
Copy Markdown
Member Author

Reworked along exactly the shape you proposed, at d420419fb (force-pushed, history squashed to one commit).

Blocker. The ledger now lives in runner-session-types.ts beside resolveRunnerDetachDecision, its only reader, so the PR adds no static edge to any Apple façade's eager closure: scripts/__tests__/eager-closure-budgets.test.ts passes locally at 691 tests with no budget moved, and Coverage is rerunning on the new head.

Design. Inline (readiness-probe) exchanges are uncharged, keyed on the readinessProbe trait that runner-readiness-routing.test.ts pins to the runner's own inlineResponse(for:) arms. The route field, the blank-id lane, and the inline-residue forgiveness are all gone; an inline answer settles nothing, and withRunnerCommandId guarantees every charged exchange carries a UUID, which also moots the shared-identity concurrency concern. Charges stay ordered by send, and a queued answer forgives at most one abandoned charge that predates it (Cubic's P2; dropping the ordering guard turns that test red — verified).

Non-blocking nits, all taken: the ledger's comments state invariants rather than issue history; the contradicting test comment is gone and that test now actually pins the invariant its comment names (markAbandoned taking another command's charge turns it red — verified); the handler reads the same lifecycle sets as settlement, with RUNNER_IN_FLIGHT_LIFECYCLE_STATES added and the rows derived from Swift's own declaration; the Swift fixture walker is replaced by a cursor-tracked blanker that survives continued case .a, headers, same-line body braces, and braces/case/default: inside strings, comments, and interpolation.

On your reachability caveat — agreed it was traced, not device-observed; the PR body records the same unresolved risk. The handoff rows are pinned through runAppleRunnerCommandWithSession's real entry against the fake runner, and device lanes passed but still don't assert the handoff, so nothing claims more than the harness proves.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at d420419. The fix looks right: the status probe no longer clears an outstanding command charge, so a real reply can still settle it. This is ready for human review.

The pre-change arithmetic trace (regression tests going red on the old code, dropping the ordering guard, markAbandoned taking another charge) comes from hand-tracing, not a run. The Swift fixture walker was only checked against today's inlineResponse(for:) arms. The shutdown handoff is proven only through the fake-runner harness via runAppleRunnerCommand; no device run confirms the abandoned-exchange-then-uptime path in practice. Whether one runner session can see concurrent executeRunnerCommandWithSession calls wasn't checked either; per-id charges make it less likely but don't rule it out.

Not blocking: charge(commandId) could take a non-optional string since withRunnerCommandId always stamps the id before charging, letting you drop the no-id test at packages/platform-apple/src/runner/runner-session-types.ts#L235; and the PR body could mention the decodeRunnerResponseBody tightening at packages/platform-apple/src/runner/runner-contract.ts#L40, which is new in this revision and tested, separate from the unrelated APP_NOT_RUNNING_RUNNER_CODE un-export that can stay or go — take or leave either.

Smoke Tests is still running on d420419 with no failure yet; since this diff touches the charge/settle path around every runner send that the iOS smoke lanes exercise, a failure there would need reading before calling it unrelated. Wait for that to finish green before merging.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 26, 2026
@thymikee
thymikee force-pushed the t3code/fix-2965-adversarial-review-pr branch from d420419 to 595b9e5 Compare September 26, 2026 15:45
@thymikee

Copy link
Copy Markdown
Member Author

Follow-up: pushed 595b9e562 for the PR head, one commit ahead of d420419fb and a one-line doc correction only (the outstandingChargeCount comment claimed a waiting seam it does not have; it is diagnostic detail). No behavior or test change. pnpm check:affected --run is green again on it — 395 files, 2,781 tests — with fallow clean on the 18 changed files. The per-finding replies above cite d420419fb, where each fix and its named mutation were verified; that commit stays reachable from this thread.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed 595b9e5. The only change since d420419 is the doc comment on outstandingChargeCount, and it is now accurate: only tests read that count, and no production path uses it. The earlier clean verdict still holds. Smoke Tests is still running at the time of this review.

@thymikee

Copy link
Copy Markdown
Member Author

CI note on 595b9e562: the iOS fixture-backed iOS simulator E2E smoke lane went red once on smoke:automation-input (wait timed out for text: Automation lab, readinessPhase: runner-start, a 10,000 ms budget against a 10,808 ms poll) and passed on re-run — 17 checks pass, 2 skipping. The only diff from d420419fb, which passed that lane, is one JSDoc sentence on outstandingChargeCount; the tree is otherwise identical, so nothing here can move runner-start latency. The same scenario and phase also fail the Smoke Tests job on three main runs (36247999237, 36163937992, 36139123059), which reads as a pre-existing device-lane flake rather than this diff. Recording it rather than claiming the lane is green-by-construction; the PR body keeps the handoff-through-harness risk open.

@thymikee
thymikee force-pushed the t3code/fix-2965-adversarial-review-pr branch from 595b9e5 to 285ee8a Compare September 27, 2026 06:19
@thymikee

thymikee commented Sep 27, 2026 •

Copy link
Copy Markdown
Member Author

Sized down for #2803 at 5c6324910 (production +270/−96, down from +291/−96; 17 files, 352 fewer test lines). Removed mechanisms:

  • the RunnerChargeSettlement object. Its refused field carried exactly one value, so it restated settled: false; settleTerminalEvidence now returns the boolean the recovery diagnostic already printed, and abandonedChargeRefused is gone (both keys were new in this PR — git grep abandonedCharge origin/main finds neither).
  • a 461-line Swift source lexer (SwiftBlanker + its suite). The reader is now the repo's own source-inspection idiom (eligibility-parity.test.ts): one regex, plus one count assertion that no case keyword went unread. That is provably as loud — mutating the real RunnerTests+Transport.swift fails for a new inline arm, a removed arm, an enum-qualified arm, a where-guarded arm, a renamed declaration, and a nested default: that truncates the capture. The bidirectional tie to readinessProbe is what makes a truncated list loud; the lexer only bought silence on shapes that already threw.
  • findCharge, and a reverse-splice loop replaced by one filter. Verified equivalent by exhaustive enumeration over 4,096 ledger/id combinations.

No regression in the fix: each mutation still turns tests red — charging probes (6), forgiving later residue (1), terminal evidence consuming an awaited charge (2), terminal evidence ignoring the id (2), markAbandoned guessing an id (11). Full check:affected --run green at 5c6324910 (394 files, 2,766 tests); eager-closure-budgets still 691 with no budget moved.

@thymikee
thymikee force-pushed the t3code/fix-2965-adversarial-review-pr branch from 285ee8a to 5c63249 Compare September 27, 2026 06:23
@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at 5c63249. The code looks correct: the inline status probe still does not clear an outstanding command charge, and the boolean settleTerminalEvidence keeps the same charge behavior as the removed RunnerChargeSettlement object. The Swift inline-arm contract is still pinned against the real RunnerTests+Transport.swift in runner-readiness-routing.test.ts, so deleting the synthetic fixture test drops no coverage.

This delta changes no device-facing behavior, so the live evidence from the 595b9e5 review still covers it. The mutation and check:affected results in the description are author-reported.

CI: Smoke Tests, Repo Guards, Coverage and CodeQL were still running when I looked. None had failed. These jobs exercise the changed route, so their result is the remaining gate.

Two optional notes. The INLINE_SWITCH reader in runner-swift-settlement-fixtures.ts stops at the first default:, so a nested switch could cut the capture short; could an indentation check close that? And the inline comment above the abandonedChargeSettled spread in runner-command-recovery.ts repeats the new @returns doc.

@thymikee
thymikee force-pushed the t3code/fix-2965-adversarial-review-pr branch from 5c63249 to 6902f10 Compare September 27, 2026 06:58
@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at 6902f10. This is clean. The new CASE_HEADER regex needs a real comma between arms, so it drops the backtracking shape of the old \s*,?\s* separator. It matches the same case headers as before on the real inlineResponse(for:) switch, on multi-line comma lists, and it still rejects a guarded case .uptime where flag: arm.

The Smoke Tests failure looks unrelated. That job timed out waiting for "Agent Device Tester" in observeFixtureHome (live-automation-scenario.ts:27), before any runner command-charge path runs, and this delta only changes a Node-side test parser.

I could not reproduce the old regex blowing up with synthetic inputs of about 50 arms. Do you have a captured slow input that is worth pinning as a regression check? This is not blocking.

Nothing here blocks a human review.

@thymikee

Copy link
Copy Markdown
Member Author

Final head for this round is 6902f103e (the comment above cites the intermediate 5c6324910; one extra commit since: CodeQL's js/polynomial-redos flagged the header regex — (?:\.\w+\s*,?\s*)+ backtracks exponentially (measured 0.7→63 ms at 12→22 items); it is now \.\w+(?:\s*,\s*\.\w+)*, flat on the same input, and the eight guard mutations above re-verified red/pass on it.

All checks green on 6902f103e: 17 pass, 2 skipping, MERGEABLE/CLEAN. One Smoke Tests lane hit the known smoke:automation-input runner-start timeout (build itself succeeded; a second attempt also died on spawnSync xcrun ETIMEDOUT — host contention, unrelated to a diff that touches zero apple/ paths) and passed on rerun.

@thymikee
thymikee merged commit 0d460b3 into main Sep 27, 2026
19 of 22 checks passed
@thymikee
thymikee deleted the t3code/fix-2965-adversarial-review-pr branch September 27, 2026 10:33
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-27 10:33 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.

2 participants