Skip to content

fix(record): warm the MP4 validator without blocking capture - #4248

Merged
thymikee merged 7 commits into
mainfrom
fix/warm-video-validator
Nov 6, 2026
Merged

thymikee merged 7 commits into
mainfrom
fix/warm-video-validator

Conversation

@thymikee

@thymikee thymikee commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Summary

Warm one MP4 validator compile per Swift cache root before output preparation without delaying capture. Stop shares it within its finalize budget without cancellation. Evict rejected compiles for later retries. Preserve main's container-only completion without xcrun. Retain compile/runtime diagnostics and recovery hints through typed failures; normalized process-exit messages include stderr excerpts. Generic Swift-cache locking is unchanged.

8 files, +413/−89 (net +324). Final round: 2 files, +24/−12; one details-factory call replaces the handwritten process-exit details. No lifecycle expansion. Replaces #4188.

agent-device record start clip.mp4
agent-device record stop

Validation

Head 9dd99cfb2be5a3712aa95adaaab1b1f845138d5b; merge base 22bd671066.

  • Node 24.13.1: pnpm check:affected --run --head 9dd99cfb2be5a3712aa95adaaab1b1f845138d5b passed, receipt run-8262HV, exactCommit: true, unchanged checkout. 347 related and 17,380 unit tests passed; one unit skipped. Both daemon-registration cases and the WebDriver timing assertion passed this round.
  • Focused: 24 passed. Disabling the process-exit marker: two failed, 22 passed; restored: 24 passed. Prior diagnostic-forwarding, regex, warm-order, pending-start and rejected-compile mutations detected regressions. Missing-xcrun compatibility comparison passed main e6ac165961 and earlier head 823b8d1d01.
  • Live at 823b8d1d01, required iPhone Air: cold start 2.237s; verified early stop 10.435s/70s; warm stop 0.794s. Simulator/daemon shut down; isolated cleanup complete.
  • Cubic threads addressed. CI coverage, provider integration and platform smoke remain required. Self-reviewed; no separate reviewer available this round.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 6.06 MB 6.03 MB -28.5 kB
Package (unpacked) 6.06 MB 6.03 MB -28.5 kB
Package (download) 1.83 MB 1.83 MB -8.3 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 37.7 ms 38.4 ms +0.7 ms
CLI --help 79.4 ms 75.7 ms -3.7 ms

@thymikee

thymikee commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

At cf964a3, I found two problems to fix before merge. All 18 checks pass; I did not run the tests myself.

On a Linux, WSL or other non-macOS daemon host, every MP4 record stop now fails. isPlayableVideo and prepareVideoValidator always go through xcrun swiftc, with no host-platform gate. There xcrun is missing, so the compile rejects with TOOL_MISSING. prepare wraps it as video_validator_unavailable (video.ts:52-54) and stores it. Base treated TOOL_MISSING as true and fell back to the container check. Head throws instead. finalize.complete reaches this from Android (platform-android/src/recording/finalize.ts:129), HarmonyOS (platform-harmonyos/src/recording/runtime.ts:163) and the capture-kit transport (screen-recording-transport.ts:122), and the Android pullPlayable calls isPlayableVideo directly. So record stop for Android, HarmonyOS and Limrun/remote devices driven from a Linux or WSL daemon fails with a non-retriable error. Its hint ("Install Swift with AVFoundation") cannot be followed there. The tests miss this because the harness stubs a fake validator for every record command on every host. The rule should be: only a macOS host needs the AVFoundation validator, and other hosts validate the container, as #4188 intended. Please make the validator a host-platform capability. On non-darwin, skip the warm-up and let isPlayableVideo return true after the container sniff it already does (readVideoContainerKind). The hostPlatform = process.platform parameter in getRecordingOverlaySupportWarning (overlay.ts:19) is a good pattern to reuse. Then add a regression with a non-darwin hostPlatform through finalize.complete, and make the harness stub macOS-only so Linux CI runs the real route.

A transient compile failure now becomes permanent. The Map keeps the wrapped promise whatever the outcome, and the .catch turns every rejection into non-retriable video_validator_unavailable. That includes a 120s xcrun timeout, process_lock_timeout when another process holds the store lock, and an abort. Base dropped a failed compile from the Map, so the next stop recompiled. Head deletes that test and adds 'stays unavailable without retrying at stop'. A stored successful path is also never revalidated, so if the stored validator file is purged from os.tmpdir(), the daemon gets TOOL_MISSING and stays stuck. One cold-host failure at the first record start can leave the daemon unable to stop any MP4 recording until it restarts, with an 'install Swift' hint that is wrong for a lock or timeout. Please delete a rejected entry from the Map so the next prepare or stop starts a fresh compile. Please also give video_validator_unavailable only to tool-absent causes, and let timeouts, lock failures and aborts keep their own typed, retriable errors. The test should assert that the next stop recompiles after a transient failure.

Could this be simpler? Keep the Map-of-promises core, drop a rejected promise, and compute needsValidator = container === 'mp4' && hostPlatform === 'darwin' in one capture-kit function used by both the warm-up and isPlayableVideo. The host then only calls warmVideoValidator(host, outputPath). Non-macOS hosts get the container check with no compile and no new typed failure. Nothing else needs to change first.

Not blocking, and you can take or leave these: the warm compile at video.ts:51 has no signal or owner, so if the daemon exits mid-compile, swiftc and the .video-validator-* temp dir are orphaned (one module-owned AbortController tied to daemon shutdown, or a follow-up issue, would cover it); src/platform-runtime-screen-recording-host.ts:30 re-implements the .webm suffix rule that readVideoContainerKind owns, which the single warmVideoValidator would also fix; the harness stub could be scoped to darwin or an explicit scenario flag, and the host test could add one Android-binding case; and the docs bullet at website/docs/docs/commands.md:1078 should say the Swift requirement applies to macOS hosts, while the isPlayableVideo docblock should name the new throw and be re-wrapped (it breaks mid-sentence at 'A WebM verdict').

For validation, the live iPhone Air run covers the Apple simulator route only, and the shared outputs.prepare and isPlayableVideo path also changed for Android, HarmonyOS and non-macOS hosts. Once the platform gate is in, a finalize.complete regression test with a non-darwin host is enough. Otherwise please show record start and record stop on a host without Swift (for example a Linux or WSL daemon with an Android emulator) returning a playable recording and not video_validator_unavailable. The local affected-check failures you reported (web shutdown smoke and the src/host.test.ts timeout) do not overlap this diff, but the cause is still unproven.

@thymikee
thymikee marked this pull request as ready for review November 5, 2026 20:02

@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 8 files

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

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

Comment thread website/docs/docs/commands.md Resolved
@apex-sentinel-staging

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

Copy link
Copy Markdown

Apex Sentinel

Verdict: approve

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

2 finding(s): 2 minor.

🟡 Minor (2)

Where Finding
packages/capture-kit/src/recording/video.ts:194 🆕 video_validator_unavailable is reported non-retriable while docs and hint say to retry record stop
packages/capture-kit/src/recording/video.ts:146 🆕 Unplayable recording is now reported as non-retriable validator-unavailable after one validator refusal

2 findings below minor not shown (user_settings.min_severity).

Reviewed with this repository's own Sentinel configuration (e6ac165961de).

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

Earlier reviews

Review of ab5a5e56 · 05.11.2026 21:58 UTC

Apex Sentinel

Verdict: approve

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

No findings.

2 findings below minor not shown (user_settings.min_severity).

Reviewed with this repository's own Sentinel configuration (e6ac165961de).

Review of 666fbf16 · 05.11.2026 21:39 UTC

Apex Sentinel

Verdict: approve

Since the last review: 0 new · 1 still open · 2 no longer raised

1 finding(s): 1 minor.

🟡 Minor (1)

Where Finding
packages/capture-kit/src/recording/video.ts:146 Compiler stderr never reaches the validator-unavailable details, so the xcrun hint is dead

Reviewed with this repository's own Sentinel configuration (e6ac165961de).

Review of 823b8d1d · 05.11.2026 20:38 UTC

Apex Sentinel

Verdict: approve

Since the last review: 3 new · 0 still open · 2 no longer raised

3 finding(s): 3 minor.

🟡 Minor (3)

Where Finding
packages/capture-kit/src/recording/video.ts:188 🆕 Validator stderr is dropped before classification, so the compile-error regex is dead
src/platform-runtime-screen-recording-host.ts:26 🆕 record start spawns swiftc on non-Apple backends and on hosts without xcrun
packages/capture-kit/src/recording/video.ts:46 🆕 Background validator compile is no longer cancellable, so a daemon exit orphans swiftc and its cache lock

1 finding below minor not shown (user_settings.min_severity).

Reviewed with this repository's own Sentinel configuration (e6ac165961de).

Review of cf964a31 · 05.11.2026 20:08 UTC

Apex Sentinel

Verdict: request_changes

2 finding(s): 1 major, 1 minor.

Required fixes

  • One transient compile failure disables MP4 verification for the daemon's lifetime

🟠 Major (1)

Where Finding
packages/capture-kit/src/recording/video.ts:43 One transient compile failure disables MP4 verification for the daemon's lifetime

🟡 Minor (1)

Where Finding
packages/capture-kit/src/recording/video.ts:134 record stop fails on daemon hosts without xcrun, where it used to succeed

Reviewed with this repository's own Sentinel configuration (e6ac165961de).

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment on lines +43 to +60
const videoValidatorCompiles = new Map<string, Promise<string>>();

// A compiled helper is valid for the Swift cache it was built into, so each cache root keeps its own.
// Callers share one compile, and each waits for it only within its own budget. The last caller to
// stop waiting cancels an unfinished compile: that kills this process's `swiftc`, or stops waiting
// for another process's compile. Nothing owns `swiftc` once its requests end, so a daemon that
// exits would leave it running.
const videoValidatorCompiles = new Map<string, SharedValidatorCompile>();
/** Starts one cached compile per Swift cache root. Callers may warm it without awaiting it. */
export function prepareVideoValidator(host: HostEnvironment): Promise<string> {
const cacheRoot = getSwiftCacheRoot(host.env);
let compiled = videoValidatorCompiles.get(cacheRoot);
if (!compiled) {
compiled = compileSwiftSourceText({
host,
source: VIDEO_VALIDATION_SCRIPT,
cacheName: 'video-validator',
}).catch((error: unknown) => {
throw unavailableVideoValidator(error);
});
videoValidatorCompiles.set(cacheRoot, compiled);
}
return compiled;
}

@apex-sentinel-staging apex-sentinel-staging Bot Nov 5, 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.

No longer raised on 823b8d1: the code it pointed at changed.

🟠 Major · One transient compile failure disables MP4 verification for the daemon's lifetime

A transient failure in ensureSwiftExecutable (Swift cache lock held by another process, or xcrun swiftc hitting its 120 s timeout) permanently disables verification: the rejected promise is cached in videoValidatorCompiles under that cache root and never removed, so every subsequent call rejects with video_validator_unavailable and retriable: false for the daemon's lifetime. The predecessor deleted the failed entry so the next caller retried; the new test locks in the no-retry behavior.

Suggested change
const videoValidatorCompiles = new Map<string, Promise<string>>();
// A compiled helper is valid for the Swift cache it was built into, so each cache root keeps its own.
// Callers share one compile, and each waits for it only within its own budget. The last caller to
// stop waiting cancels an unfinished compile: that kills this process's `swiftc`, or stops waiting
// for another process's compile. Nothing owns `swiftc` once its requests end, so a daemon that
// exits would leave it running.
const videoValidatorCompiles = new Map<string, SharedValidatorCompile>();
/** Starts one cached compile per Swift cache root. Callers may warm it without awaiting it. */
export function prepareVideoValidator(host: HostEnvironment): Promise<string> {
const cacheRoot = getSwiftCacheRoot(host.env);
let compiled = videoValidatorCompiles.get(cacheRoot);
if (!compiled) {
compiled = compileSwiftSourceText({
host,
source: VIDEO_VALIDATION_SCRIPT,
cacheName: 'video-validator',
}).catch((error: unknown) => {
throw unavailableVideoValidator(error);
});
videoValidatorCompiles.set(cacheRoot, compiled);
}
return compiled;
}
const videoValidatorCompiles = new Map<string, Promise<string>>();
/** Starts one cached compile per Swift cache root. Callers may warm it without awaiting it. */
export function prepareVideoValidator(host: HostEnvironment): Promise<string> {
const cacheRoot = getSwiftCacheRoot(host.env);
let compiled = videoValidatorCompiles.get(cacheRoot);
if (!compiled) {
compiled = compileSwiftSourceText({
host,
source: VIDEO_VALIDATION_SCRIPT,
cacheName: 'video-validator',
}).catch((error: unknown) => {
// Only a toolchain that cannot provide AVFoundation is permanently unavailable; a lock or
// compile timeout must let the next caller try again.
if (videoValidatorCompiles.get(cacheRoot) === compiled) videoValidatorCompiles.delete(cacheRoot);
throw unavailableVideoValidator(error);
});
videoValidatorCompiles.set(cacheRoot, compiled);
}
return compiled;
}

Suggested change

Drop the map entry when the failure is transient (anything not isSwiftVideoValidatorError), re-check isExecutableFile/the cache before refusing so an executable another process published later is usable, keep unconditional refusal only for a confirmed missing toolchain, and consider retriable: true for the transient class.

Details

Also raised here

Also raised at this location by persona maintainability: A transient validator compile failure poisons the cache for the whole daemon lifetime — If the one compile started at record start fails for a transient reason — the daemon under load hits the xcrun timeout, the Swift cache lock is held by a sibling process, an I/O hiccup — every later record stop in that daemon rejects with video_validator_unavailable, retriable: false, and the hint "Install the Swift toolchain with AVFoundation", which is wrong advice for a timeout and never clears short of a daemon restart. The rejected promise is what gets stored in videoValidatorCompiles, so the map caches the failure permanently; the predecessor deleted the entry on rejection (if (videoValidatorCompiles.get(cacheRoot) === shared) videoValidatorCompiles.delete(cacheRoot)) so the next caller recompiled. Keep the entry only on success, or store a wrapper that clears the key on rejection, so a retriable cause is retried at the next stop.

persona: senior-developer, javascript, maintainability

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

Comment on lines +134 to 143
if (isSwiftVideoValidatorUnavailable(result.stderr, result.stdout)) {
throw unavailableVideoValidator(new AppError('COMMAND_FAILED', result.stderr));
}
return false;
} catch (error) {
if (isSwiftVideoValidatorError(error)) {
return true;
throw unavailableVideoValidator(error);
}
throw error;
}

@apex-sentinel-staging apex-sentinel-staging Bot Nov 5, 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.

No longer raised on 823b8d1, with the code it pointed at unchanged: withdrawn, not fixed.

🟡 Minor · record stop fails on daemon hosts without xcrun, where it used to succeed

record stop for an MP4 recording on a host without xcrun (Linux, Windows, or macOS with only Command Line Tools) now fails with COMMAND_FAILED / details.reason: video_validator_unavailable, retriable: false; previously a missing validator meant "trust the container sniff" and the recording finalized. isPlayableVideo runs runCmd('xcrun', ...) to compile the validator, gets TOOL_MISSING, and throws instead of returning true as the predecessor's isSwiftVideoValidatorError branch did. The failure is cached per cache root, so no retry clears it without a daemon restart.

Suggested change

Decide whether a host with no Swift toolchain may still finalize recordings: if yes, treat TOOL_MISSING as "accept the container sniff" as before and reserve video_validator_unavailable for a toolchain that exists but fails; either way state the decision in website/docs/docs/commands.md.

Details

Also raised here

Also raised at this location by persona senior-developer: Missing xcrun now hard-fails Android/HarmonyOS record stop instead of finalizing — record stop on an Android or HarmonyOS recording fails with non-retriable video_validator_unavailable on any host without a working Swift toolchain — e.g. a Linux daemon, the common setup for ADB recording. xcrun is absent there, so the compile throws TOOL_MISSING, isSwiftVideoValidatorError matches it, and isPlayableVideo now throws where it previously returned true; the Android pull path (src/platform-runtime-screen-recording-android-host.ts:112) and the finalizer propagate that error, so the MP4 the device already produced is never reported. The hint then tells the operator to install Swift with AVFoundation on the host, which is not how an Android recording is verified. The error should stay scoped to hosts that actually need the MP4 validator, or the WebM/absent-toolchain case must keep its old verdict.

persona: javascript, senior-developer

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

@thymikee

thymikee commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

Review round at 823b8d1: evicted rejected compiles, preserved main's missing-xcrun completion, added TOOL_MISSING/warm-order coverage, and moved/narrowed recovery docs. Round: +100/−54 across 6 files; PR: +332/−87 across 8 files. The same compatibility test passes on main e6ac165 and head. All 22 focused tests and the exact-head affected gate pass (run-AVNTtN). Fresh Air verification and cleanup pass. Five Cubic threads replied to and resolved; CI is running.

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment on lines 26 to 33
prepare: async (outputPath) => {
if (!outputPath.toLowerCase().endsWith('.webm')) {
const { prepareVideoValidator } = await import('@agent-device/capture-kit/recording-video');
void prepareVideoValidator(host).catch(() => {});
}
const { createScreenRecordingOutputHost } =
await import('./platform-runtime-screen-recording-output-host.ts');
await createScreenRecordingOutputHost().prepare(outputPath);

@apex-sentinel-staging apex-sentinel-staging Bot Nov 5, 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.

No longer raised on 666fbf1, with the code it pointed at unchanged: withdrawn, not fixed.

🆕 🟡 Minor · record start spawns swiftc on non-Apple backends and on hosts without xcrun

record start clip.mp4 on Android, Web or other non-Apple backends calls prepareVideoValidator on the host, which on macOS launches a ~30s swiftc compile during recording prep and on Linux/CI spawns a command that fails with ENOENT on every start (repeats because the failure is evicted from the map). outputs.prepare is the shared hook for every backend, but only Apple's finalize path and Android's pull probe need the validator.

Suggested change

Warm the validator only for the Apple backend, instead of for every non-.webm output path prepared by any backend.

Details

Also raised here

Also raised at this location by persona javascript: Validator warm-up spawns xcrun and creates Swift cache dirs on hosts that have no Apple toolchain — The new warm-up fires for every non-.webm output before the backend is known, so an Android, HarmonyOS or Limrun recording on a Linux daemon host runs getSwiftCacheRoot plus buildSwiftToolEnv (two mkdirSync calls creating home/ and module-cache/ under the cache root, via swift-cache.ts:81-82) and then spawns xcrun, whose ENOENT is dropped by the detached .catch(() => {}). Nothing observable is wrong today, but the work is unconditional for backends and hosts where the Apple validator is not what produced the file, and the spawned-process and directory side effects are silent. Consider gating on the platform (darwin) or letting the finalize path, which already knows the container, start the compile.

persona: senior-developer, javascript

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

Comment on lines +46 to +62
export function prepareVideoValidator(host: HostEnvironment): Promise<string> {
const cacheRoot = getSwiftCacheRoot(host.env);
let compiled = videoValidatorCompiles.get(cacheRoot);
if (!compiled) {
compiled = compileSwiftSourceText({
host,
source: VIDEO_VALIDATION_SCRIPT,
cacheName: 'video-validator',
}).catch((error: unknown) => {
if (videoValidatorCompiles.get(cacheRoot) === compiled)
videoValidatorCompiles.delete(cacheRoot);
throw unavailableVideoValidator(error);
});
videoValidatorCompiles.set(cacheRoot, compiled);
}
return compiled;
}

@apex-sentinel-staging apex-sentinel-staging Bot Nov 5, 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.

No longer raised on 666fbf1, with the code it pointed at unchanged: withdrawn, not fixed.

🆕 🟡 Minor · Background validator compile is no longer cancellable, so a daemon exit orphans swiftc and its cache lock

prepareVideoValidator fires a detached compile with no signal or owner (compileSwiftSourceText called without timeoutMs, falling back to the 120s default). A daemon exit during warm-up orphans swiftc holding <executablePath>.lock; the next daemon's warm-up waits on that lock and can report a non-retriable video_validator_unavailable caused by a dead process.

Suggested change

Keep an owner for the background compile (a daemon-lifetime AbortController passed as signal, or an unref'd bound timeout), or state in CLI text/docs that a daemon exiting mid-warm-up leaves swiftc running.

Details

persona: javascript

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

@thymikee

thymikee commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

At 823b8d1, the code looks good, and both problems from the earlier review are fixed: a missing validator tool now falls back to the container-only completion main uses, so non-macOS hosts no longer fail every MP4 record stop, and a rejected compile is evicted instead of staying for the life of the daemon. There are no conflicts. Most checks pass; Coverage was still running. I traced the Linux path through the unit and host tests with a mocked command runner and did not run a non-macOS host.

Not blocking, take or leave: the provider-scenario harness at test/integration/provider-scenarios/harness.ts:244 still installs a fake validator on every host, so no scenario reaches the TOOL_MISSING fallback or real AVFoundation validation; stubbing only on darwin would fix that. And prepareVideoValidator (packages/capture-kit/src/recording/video.ts:120) wraps every compile failure that isPlayableVideo then unwraps, which also marks transient compile failures as non-retriable although the docs say the next start or stop retries them; rejecting with the raw error and wrapping once in isPlayableVideo would remove the cause check.

The open inline threads on rejected-compile eviction and TOOL_MISSING handling no longer apply at this head and can be resolved.

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

thymikee commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

Pushed 666fbf16fd: preserve compiler stderr/stdout, exit details, recovery hints and original cause through the typed validator error. The real no such module 'AVFoundation' message is injected at runCmd through the finalizer; reverting diagnostic forwarding fails 2 tests, restoring the old regex fails 3, restored suite passes 23. This round: 3 files, +50/−3. Other minors unchanged.

check:affected remains failed (run-LvslVt): 347 related passed; one WebDriver elapsed-abort assertion failed among 17,380 unit cases. That file passes 22 tests on both parent/head in isolation; attribution is unproven. Docs checks passed separately. CI must validate 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 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

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 1 finding moved to a new line on 666fbf1. The verdict and the full list are in the summary comment.

Comment on lines +146 to +148
if (isSwiftVideoValidatorUnavailable(result.stderr, result.stdout)) {
throw unavailableVideoValidator(new AppError('COMMAND_FAILED', result.stderr));
}

@apex-sentinel-staging apex-sentinel-staging Bot Nov 5, 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.

No longer raised on ab5a5e5: the code it pointed at changed.

🟡 Minor · Compiler stderr never reaches the validator-unavailable details, so the xcrun hint is dead

Validator writes no such module 'AVFoundation' on stderr; record stop returns video_validator_unavailable with the generic repair hint and no stderr in details. unavailableVideoValidator reads cause.details?.stderr to pick the hint, but the only caller throws new AppError('COMMAND_FAILED', result.stderr) with details undefined, so that branch is unreachable for a run failure. The unavailable.mp4 tests assert only reason and retriable, which is why coverage misses it.

Suggested change
if (isSwiftVideoValidatorUnavailable(result.stderr, result.stdout)) {
throw unavailableVideoValidator(new AppError('COMMAND_FAILED', result.stderr));
}
if (isSwiftVideoValidatorUnavailable(result.stderr, result.stdout)) {
throw unavailableVideoValidator(
new AppError('COMMAND_FAILED', result.stderr, {
stderr: result.stderr,
stdout: result.stdout,
exitCode: result.exitCode,
}),
);
}
Details

Also raised here

Also raised at this location by persona maintainability: Validator run failure loses stderr in details, so the AVFoundation hint is never used — When the compiled validator itself runs and reports no such module 'AVFoundation' on stderr, the user gets the generic "Repair the Swift toolchain or validator executable" hint and no details.stderr, even though the code just classified it as an AVFoundation/xcrun problem. Pass stderr/stdout/exitCode as details of the thrown AppError. This is also why none of the existing tests catch it — the unavailable.mp4 cases assert only reason and retriable, not hint or stderr, unlike the compiler-diagnostics case which does assert both.

Also raised at this location by persona senior-developer: Validator's own stderr is passed as the message, so the AVFoundation hint never fires — The cause is that line 147 puts the validator's stderr into the AppError message (new AppError('COMMAND_FAILED', result.stderr)) and not into details.stderr, so the classifier at lines 200-203, which only reads details?.stderr/details?.stdout, cannot see it and picks the other branch. The compile-failure path does populate details.stderr (the new preserves compiler stderr test covers it), which is why the hint is right there and wrong here. Pass the diagnostics as details so both paths classify identically.

persona: maintainability, senior-developer

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

@thymikee

thymikee commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

At 666fbf1, the code still looks good and the review at 823b8d1 stands. The new commit only carries compiler stderr/stdout, exit details and recovery hints through the typed validator error, and the stderr hint is now tested in video.test.ts. There are no conflicts. CI was still running when I reviewed; this delta touches only capture-kit video.ts and two unit tests.

The inline threads on non-macOS warm-up, the detached compile on daemon exit and the stderr in the cause message still stand but need no change before merge. The threads on rejected-compile eviction, the TOOL_MISSING fallback and the forwarded stderr hint are fixed and can be resolved.

@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

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment on lines +194 to +209
return new AppError(
'COMMAND_FAILED',
'The video validator is unavailable; MP4 playability could not be verified.',
{
...details,
reason: 'video_validator_unavailable',
retriable: false,
hint:
details?.hint ??
(isSwiftVideoValidatorUnavailable(details?.stderr ?? '', details?.stdout ?? '')
? 'Ensure Swift and AVFoundation are available on the daemon host, then retry record stop.'
: 'Repair the Swift toolchain or validator executable on the daemon host, then retry record stop.'),
},
cause,
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🆕 🟡 Minor · video_validator_unavailable is reported non-retriable while docs and hint say to retry record stop

A compile failure (e.g. xcrun can't find swiftc due to a wrong DEVELOPER_DIR, or the 120s timeout) returns details.retriable:false, but the docs bullet added here and the generated hint both say "Repair the reported failure and retry record stop", and prepareVideoValidator evicts the rejected compile so the next attempt re-runs it. A client honouring retriable:false will therefore not retry a transient failure the code path supports retrying. Cause: unavailableVideoValidator hardcodes retriable:false for every cause without distinguishing transient compile failures from a permanently broken validator executable.

Suggested change

Derive retriable from the cause—true for a compile failure or timeout that the next attempt re-runs, false only for a validator executable that cannot work—and make the docs and hint agree with that signal.

Details

Also raised here

Also raised at this location by persona maintainability: A retriable compile failure is reported as non-retriable, contradicting the retry guidance — A transient compile failure (swiftc killed by OOM, a cache-lock contention failure) is reported as retriable: false, yet the same change evicts the rejected compile so the next attempt recompiles, and the new cliDetail and commands.md both say "Repair the reported failure and retry record stop; a failed compile is retried on the next start or stop". unavailableVideoValidator hard-codes retriable: false for every cause, so a client that honours details.retriable refuses the retry the docs instruct, and the flag contradicts the author's own guidance for the compile-failure case. Carry the cause's retriability through (or mark compile failures retriable) instead of forcing false.

persona: javascript, senior-developer, maintainability

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

Comment on lines +146 to 157
if (isSwiftVideoValidatorUnavailable(result.stderr, result.stdout)) {
throw unavailableVideoValidator(
new AppError('COMMAND_FAILED', result.stderr, execFailureDetails(result)),
);
}
return false;
} catch (error) {
if (isSwiftVideoValidatorError(error)) {
return true;
throw unavailableVideoValidator(error);
}
throw error;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🆕 🟡 Minor · Unplayable recording is now reported as non-retriable validator-unavailable after one validator refusal

A corrupt MP4 (ftyp+moov present but unplayable) whose validator exits non-zero with an 'unavailable' string (e.g. stderr containing xcrun: error: unable to find utility "swiftc") fails record stop with retriable:false / reason video_validator_unavailable instead of the retriable RECORDING_OUTPUT_UNPLAYABLE_REASON. isPlayableVideo now throws out of the exitCode !== 0 branch (video.ts:146-151), and finalizeScreenRecording (platform-runtime-screen-recording-finalizer-host.ts:38-40) has no catch for it, so the unplayable-recording refusal is never reached.

Suggested change

Either classify a non-zero validator exit as an unplayable recording and keep the throw for launch-time failures only, or catch video_validator_unavailable in finalizeScreenRecording and re-raise it with retriable:true.

Details

persona: javascript

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

@thymikee

thymikee commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

I found no problems in the code at 9dd99cf, and the earlier clean review at 823b8d1 still holds. The validator failure now passes its stderr and stdout as error details, so the hint classifier can read them, and the unavailable cases in video.test.ts assert this. A rejected compile is evicted only while it is still the shared pending compile, and a missing compiler still falls back to the container-only check.

All 19 checks pass at this commit, and there are no conflicts. The live iPhone Air check at 823b8d1 still covers this head, because the delta only changes error details on the unavailable path.

Nothing blocks merge. Of the open threads, r4486721790 (retriable on a compile timeout) still applies but is optional: either keep compile timeouts retriable or reword the docs line. r4486721798 (unplayable versus unavailable) does not apply, because a corrupt MP4 still returns false and raises the unplayable error; only missing-toolchain output throws. The other open threads are fixed or harmless and can be resolved.

@thymikee
thymikee merged commit be7d703 into main Nov 6, 2026
19 checks passed
@thymikee
thymikee deleted the fix/warm-video-validator branch November 6, 2026 04:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant