Skip to content

fix: Drivers CI Compile Gate + Audit Defect Fixes - #167

Merged
droid-ash merged 5 commits into
mainfrom
260730-zga4-drivers-ci-gate-audit-defects
Jul 30, 2026
Merged

droid-ash merged 5 commits into
mainfrom
260730-zga4-drivers-ci-gate-audit-defects

Conversation

@droid-ash

@droid-ash droid-ash commented Jul 30, 2026 •

Copy link
Copy Markdown
Owner

Meta

Change ID Type Confidence Plan Review
zga4 fix 4.0/5.0 16/16 tasks, 36/36 acceptance ✓ ✓ 2 cycles
Impact +/− Net
true +3179 / −104 +3075
└ impl +2412 / −95 +2317
└ tests +767 / −9 +758

generated by fab-kit v2.16.8

Pipeline: intake ✓ → apply ✓ → review ✓ → hydrate ✓ → ship → review-pr

Summary

A repo-wide comment-quality audit of all 234 code files surfaced ~20 confirmed runtime defects and the fact that drivers/ (Kotlin + Swift) has no CI verification of any kind — no workflow builds either native driver, and test_paths couldn't even match a native test file. This PR adds .github/workflows/drivers.yml (Android + iOS compile gate) and fixes 15 of those defects, split across the native drivers, the gRPC contract, device-node log/recording/recovery handling, cli host preflight, and cloud-core's timeout parser.

Changes

  • A1. New CI workflow: drivers compile gate (Android debug+androidTest APKs, iOS build-for-testing; compile-only, additive to ci.yml)
  • A2. fab/project/config.yaml — test_paths now matches native Kotlin/Swift test files
  • B1. Android tapPercent — was dividing a 0–1 fraction by 100 (wrong for every input)
  • B2. Android tapPercent — 0.0 (a valid left/top-edge coordinate) was rejected
  • B3. iOS screenshot quality default corrected from 10 to 5, matching Android/proto/TS client
  • B4. iOS fps calculation — integer division always yielded 0; now floating point
  • B5. Log write-streams are now .end()ed and flushed instead of just unpiped
  • B6. Recording rename no longer preceded by a rm that could strand a failed rename
  • B7. Driver recovery no longer re-executes mutating actions after a restart
  • B8. _disconnectionCallback is now actually invoked on the disconnection path
  • B9. checkCommandOnPath now uses where on Windows instead of always which
  • B10. upload.ts's timeout parser now shares submit.ts's Number.isInteger check (was Number.isFinite, silently accepting 1.5)
  • B11. iOS no longer fabricates success = true for five no-op actions
  • B12. killDriver renamed to closeDriverChannel / documented (no behaviour change)
  • B13. driver.proto — reserved 4; replaces a prose comment about the removed field
  • B14. AccessibilityStreamer — deleted a false "5-second bounds stability" guarantee (no behaviour change)
  • B15. AccessibilityStreamer — documented the three divergent hierarchy traversals (no behaviour change)

Verification & risk — please read before reviewing

1. The five native fixes (B1–B4, B14 / plan R4–R8, Kotlin + Swift) are compile-verified only. There is no Kotlin/Swift unit-test harness in this repo — drivers/android/app/src/androidTest/ is the driver (a single @Test starts the gRPC server and blocks forever, zero assertions), and iOS has no test target at all. This change does not create one. .github/workflows/drivers.yml has never run before this PR — it was built in an environment with no JDK, no Android SDK, and no Xcode. This PR is the first real execution of that workflow, so its own android and ios jobs are the first verification of both the new gate itself and the native fixes it gates. If either job fails, treat that as the gate doing its job, not as a surprise.

2. Most consequential fix, user-visible: Android tapPercent computed (xPercent * screenWidth) / 100, but the TypeScript client always sends 0–1 fractions, never 0–100. A centre tap (0.5, 0.5) on a 1080×1920 screen landed at (5, 9) — the top-left corner — for every input. It's now (540, 960), matching iOS, which was already correct.

3. One behaviour change has a live blast radius. B11 makes iOS stop returning success = true for five actions it never performed (copyText, pasteText, hideKeyboard, getAppList, setLocation). Of those five, only hideKeyboard is reachable from TypeScript via the driver today (IOSSimulator.ts → CommonDriverActions.hideKeyboard() → the gRPC HideKeyboard RPC). A planner step emitting PLANNER_ACTION_HIDE_KEYBOARD on iOS now returns success:false where it previously returned success:true — intended (this is the defect B11 fixes), but worth knowing before you see it in a run report.

Also worth knowing:

  • One should-fix was deliberately left open: packages/device-node/src/device/logWriteStream.ts's open() has no persistent error listener on the write stream — pre-existing in kind, judged outside this change's B5 scope.
  • drivers.yml carries no timeout-minutes on either job. This matters more for ios, which runs on macos-latest at GitHub's 10× per-minute billing multiplier.

Test/lint status: 539 tests passing (up from a 517 baseline before this change), lint unchanged at 42 warnings / 0 errors. Review passed after one rework cycle; 36/36 acceptance items verified for TypeScript-side behaviour and source-verified (not test-verified) for the native fixes.

Summary by CodeRabbit

  • New Features
    • Added a “Drivers” CI compile-only gate for Android and iOS, running on pull requests and main-branch pushes.
    • Improved CLI host preflight command lookup across Windows vs non-Windows.
  • Bug Fixes
    • Fixed driver contract behavior for fraction-based coordinates, default screenshot quality, streaming FPS clamping, and clearer unsupported iOS action failures.
    • Improved stability around log capture finalization, recording replacement, disconnection notifications, and driver restart replay rules.
  • Documentation
    • Updated CI quality gate and expanded Drivers, CLI, device logging, and gRPC driver contract documentation, including cloud timeout validation.

Adds .github/workflows/drivers.yml (Android + iOS compile-only build,
never previously executed) and fixes ~15 confirmed defects from a
repo-wide comment-quality audit: Android tapPercent divided a 0-1
fraction by 100 (every percent tap landed near the top-left corner),
iOS fabricated success for five no-op actions, log write-streams were
never ended (truncated logs), a recording could be lost on a failing
rename, driver recovery could replay mutating actions, and more.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 52d97981-0d07-4d04-8df9-bbb89720e877

📥 Commits

Reviewing files that changed from the base of the PR and between be61f36 and 12eda40.

📒 Files selected for processing (2)
  • fab/changes/260730-zga4-drivers-ci-gate-audit-defects/.history.jsonl
  • fab/changes/260730-zga4-drivers-ci-gate-audit-defects/.status.yaml

📝 Walkthrough

Walkthrough

Adds a compile-only Android/iOS drivers workflow and fixes cross-platform driver contracts, device-node reliability, CLI PATH probing, cloud timeout parsing, test-path discovery, and supporting documentation and change records.

Changes

Drivers CI and change planning

Layer / File(s) Summary
Native compile gate and repository planning
.github/workflows/drivers.yml, fab/changes/..., fab/project/config.yaml, docs/memory/ci/*
Adds Android and iOS compile-only jobs, expands native test-path discovery, and records workflow requirements, acceptance criteria, and CI contracts.
Cross-language driver contracts
proto/finalrun/driver.proto, drivers/android/..., drivers/ios/..., docs/memory/drivers/*
Aligns coordinate fractions, screenshot defaults, streaming FPS handling, unsupported iOS RPC responses, protobuf tag reservation, and hierarchy traversal documentation.

Device-node runtime reliability

Layer / File(s) Summary
Log capture finalization
packages/device-node/src/device/logWriteStream.ts, *LogProvider.ts, device/test/logCaptureProviders.test.ts, docs/memory/device-node/log-capture.md
Introduces registry-based draining, ending, flushing, timeout handling, and cleanup for Android and iOS log streams.
Recovery, recording, and lifecycle behavior
IOSRecordingProvider.ts, IOSSimulator.ts, Device.ts, CommonDriverActions.ts, DeviceRuntime.ts, related tests/docs
Preserves recordings on rename failure, restricts recovery replay, notifies disconnections once per registration, and clarifies channel-closing behavior.

CLI and cloud contracts

Layer / File(s) Summary
Command lookup and timeout parsing
packages/cli/src/hostPreflight.ts, packages/cli/src/test/hostPreflight.test.ts, packages/cloud-core/src/{timeoutEnv,submit,upload}.ts, related tests/docs
Adds injectable Windows/POSIX command resolution and shares strict positive-integer timeout parsing between submit and upload.

Estimated code review effort: 5 (Critical) | ~90+ minutes

Possibly related PRs

Suggested labels: bug

Suggested reviewers: arnoldlaishram

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a new Drivers CI compile gate plus audit defect fixes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 260730-zga4-drivers-ci-gate-audit-defects

Comment @coderabbitai help to get the list of available commands.

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
fab/changes/260730-zga4-drivers-ci-gate-audit-defects/plan.md (1)

457-457: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Make the Swift isolation requirement explicit before marking R5 complete.

The task still records a Swift 6 concurrency risk for defaultScreenshotQuality being read from nonisolated methods while declared in an @MainActor class. Prefer private nonisolated static let, or verify the exact project toolchain accepts the current declaration in the new workflow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fab/changes/260730-zga4-drivers-ci-gate-audit-defects/plan.md` at line 457,
Update the defaultScreenshotQuality declaration in GrpcDriverServer.swift to
explicitly mark the static constant nonisolated, or verify and document that the
project’s Swift 6 toolchain accepts the current isolation. Prefer the private
nonisolated static let declaration so the nonisolated getScreenshot and
getRawScreenshot methods can access it safely before marking R5 complete.
packages/device-node/src/device/IOSRecordingProvider.ts (1)

199-235: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Rename-over-delete fix looks correct.

Not deleting the original before the atomic same-directory rename correctly closes the "recording destroyed on rename failure" defect; verified against the new tests.

One minor note: if fsp.rename throws (line 234), the compressed file produced by ffmpeg is left behind with no cleanup — low risk given same-directory renames rarely fail, but a stray file could accumulate on repeated failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/device-node/src/device/IOSRecordingProvider.ts` around lines 199 -
235, Update _compressVideo to clean up compressedFilePath when fsp.rename fails,
while preserving the original file and propagating the rename error. Scope
cleanup to the rename failure path and avoid removing the compressed file after
a successful rename.
docs/memory/device-node/log-capture.md (1)

61-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Outer-catch trigger list omits the success-path finalize() failing.

This says finalizeQuietly runs in the outer catch "when _waitForExit throws," but in AndroidLogcatProvider.stopLogCapture/IOSLogProvider.stopLogCapture the success-path finalize() call (described two lines above as "the success path's call") sits in the same try block. If that finalize() rejects (a write/flush error), it also lands in this outer catch, not just a _waitForExit rejection. Worth naming both triggers so a reader doesn't conclude the redundant finalizeQuietly only guards against _waitForExit.

✏️ Proposed wording
-  (an early return), and each provider's outer `catch` when `_waitForExit` throws. Those paths still
+  (an early return), and each provider's outer `catch` when `_waitForExit` throws or the success
+  path's own `finalize` call throws. Those paths still
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/memory/device-node/log-capture.md` around lines 61 - 70, Update the
documentation describing the outer catch paths for
AndroidLogcatProvider.stopLogCapture and IOSLogProvider.stopLogCapture to state
that finalizeQuietly also handles failures when the success-path finalize call
rejects, in addition to _waitForExit throwing. Keep the distinction between
throwing finalize and swallowing finalizeQuietly unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@drivers/ios/finalrun-ios-test/Managers/XCViewHierarchyManager.swift`:
- Around line 64-69: Clamp the requested FPS in the gRPC streaming handler
before calculating frameDelayNs, using the same 1...60 bounds as
XCViewHierarchyManager’s startStreaming flow. Update the handler in
GrpcDriverServer around the frame-delay calculation so zero or out-of-range
requests are normalized before division, while preserving valid requested FPS
values.

In `@fab/changes/260730-zga4-drivers-ci-gate-audit-defects/plan.md`:
- Line 470: Document the retained public killDriver APIs in DeviceRuntime,
DeviceAgent, and the Device.killDriver() facade to explicitly state that they
close the driver channel rather than terminate the process. Keep the existing
signatures and behavior unchanged, and update the acceptance claim to reflect
that all three declarations are documented.
- Around line 464-465: Extend logCaptureProviders.test.ts with regression tests
covering AndroidLogcatProvider and IOSLogProvider when SIGINT delivery or
_waitForExit fails, verifying stopLogCapture still finalizes the log stream and
cleans up its registry entry. Exercise each provider error path and assert the
output is finalized without requiring a second stop.

In `@packages/device-node/src/device/ios/test/IOSSimulator.test.ts`:
- Around line 599-600: Remove the duplicate block-scoped declarations causing
TypeScript compilation failures: in
packages/device-node/src/device/ios/test/IOSSimulator.test.ts lines 599-600,
keep only one tapCalls declaration within the IOSSimulator mutating-action test;
in packages/device-node/src/device/test/Device.test.ts lines 341-342, keep only
one notifications declaration.

In `@packages/device-node/src/grpc/GrpcDriverClient.ts`:
- Around line 345-349: Update launchApp() to stop configuring retries for the
mutating launch request: remove or set its maxRetries to the non-retrying value
used by _unaryCall(). Preserve retry behavior for read-only and explicitly
replayable operations, while ensuring a launch is issued only once before driver
recovery.

---

Nitpick comments:
In `@docs/memory/device-node/log-capture.md`:
- Around line 61-70: Update the documentation describing the outer catch paths
for AndroidLogcatProvider.stopLogCapture and IOSLogProvider.stopLogCapture to
state that finalizeQuietly also handles failures when the success-path finalize
call rejects, in addition to _waitForExit throwing. Keep the distinction between
throwing finalize and swallowing finalizeQuietly unchanged.

In `@fab/changes/260730-zga4-drivers-ci-gate-audit-defects/plan.md`:
- Line 457: Update the defaultScreenshotQuality declaration in
GrpcDriverServer.swift to explicitly mark the static constant nonisolated, or
verify and document that the project’s Swift 6 toolchain accepts the current
isolation. Prefer the private nonisolated static let declaration so the
nonisolated getScreenshot and getRawScreenshot methods can access it safely
before marking R5 complete.

In `@packages/device-node/src/device/IOSRecordingProvider.ts`:
- Around line 199-235: Update _compressVideo to clean up compressedFilePath when
fsp.rename fails, while preserving the original file and propagating the rename
error. Scope cleanup to the rename failure path and avoid removing the
compressed file after a successful rename.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 525f4c3e-7e84-4f4b-adaa-4c65ae017235

📥 Commits

Reviewing files that changed from the base of the PR and between 19e995b and 118bc4b.

📒 Files selected for processing (48)
  • .github/workflows/drivers.yml
  • docs/memory/ci/index.md
  • docs/memory/ci/pr-quality-gate.md
  • docs/memory/cli/host-preflight.md
  • docs/memory/cli/index.md
  • docs/memory/cloud-core/index.md
  • docs/memory/cloud-core/submit-pipeline.md
  • docs/memory/device-node/android-ios-mirror.md
  • docs/memory/device-node/index.md
  • docs/memory/device-node/log-capture.md
  • docs/memory/drivers/grpc-contract.md
  • docs/memory/drivers/index.md
  • docs/memory/drivers/log.md
  • docs/memory/index.md
  • drivers/android/app/src/androidTest/java/app/finalrun/android/TestUtils.kt
  • drivers/android/app/src/androidTest/java/app/finalrun/android/data/hierarchy/AccessibilityStreamer.kt
  • drivers/ios/finalrun-ios-test/GrpcDriverServer.swift
  • drivers/ios/finalrun-ios-test/Managers/XCViewHierarchyManager.swift
  • fab/changes/260730-zga4-drivers-ci-gate-audit-defects/.history.jsonl
  • fab/changes/260730-zga4-drivers-ci-gate-audit-defects/.status.yaml
  • fab/changes/260730-zga4-drivers-ci-gate-audit-defects/intake.md
  • fab/changes/260730-zga4-drivers-ci-gate-audit-defects/plan.md
  • fab/changes/archive/2026/07/260730-ggeg-drivers-ci-gate-audit-defects/.history.jsonl
  • fab/changes/archive/2026/07/260730-ggeg-drivers-ci-gate-audit-defects/.status.yaml
  • fab/changes/archive/index.md
  • fab/project/config.yaml
  • packages/cli/src/hostPreflight.ts
  • packages/cli/src/test/hostPreflight.test.ts
  • packages/cloud-core/src/submit.ts
  • packages/cloud-core/src/test/submit.test.ts
  • packages/cloud-core/src/timeoutEnv.ts
  • packages/cloud-core/src/upload.ts
  • packages/common/src/interfaces/DeviceAgent.ts
  • packages/device-node/src/device/AndroidLogcatProvider.ts
  • packages/device-node/src/device/Device.ts
  • packages/device-node/src/device/IOSLogProvider.ts
  • packages/device-node/src/device/IOSRecordingProvider.ts
  • packages/device-node/src/device/android/AndroidDevice.ts
  • packages/device-node/src/device/ios/IOSSimulator.ts
  • packages/device-node/src/device/ios/test/IOSSimulator.test.ts
  • packages/device-node/src/device/logWriteStream.ts
  • packages/device-node/src/device/shared/CommonDriverActions.ts
  • packages/device-node/src/device/shared/DeviceRuntime.ts
  • packages/device-node/src/device/test/Device.test.ts
  • packages/device-node/src/device/test/IOSRecordingProvider.test.ts
  • packages/device-node/src/device/test/logCaptureProviders.test.ts
  • packages/device-node/src/grpc/GrpcDriverClient.ts
  • proto/finalrun/driver.proto

Comment thread drivers/ios/finalrun-ios-test/Managers/XCViewHierarchyManager.swift
Comment thread fab/changes/260730-zga4-drivers-ci-gate-audit-defects/plan.md
Comment thread fab/changes/260730-zga4-drivers-ci-gate-audit-defects/plan.md
Comment thread packages/device-node/src/device/ios/test/IOSSimulator.test.ts
Comment thread packages/device-node/src/grpc/GrpcDriverClient.ts Outdated
droid-ash and others added 2 commits July 30, 2026 10:34
Clamp the gRPC StartStreaming fps into 1...60 before it is used as a
divisor in GrpcDriverServer.startStreaming. An unclamped request.fps of 0
made `1_000_000_000 / fps` an integer division by zero and a negative
value made `UInt64(...)` a negative-to-unsigned conversion — both trap in
Swift and take the XCUITest runner, and the whole driver, down with it.
R6 clamped the legacy timer path in XCViewHierarchyManager; this is the
same guard on the live gRPC path.

Correct _unaryCall's retry doc in GrpcDriverClient: it claimed every
maxRetries:2 opt-in is read-only, but launchApp opts in and a launch is
mutating. The retry is pre-existing and load-bearing for iOS driver
recovery, so it is documented as the one exception rather than silently
narrowed (a behaviour change, out of scope).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZELnpYMz46gN9meS2j4GC
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZELnpYMz46gN9meS2j4GC
@droid-ash
droid-ash marked this pull request as ready for review July 30, 2026 16:42
@droid-ash
droid-ash merged commit 0ad8580 into main Jul 30, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant