Hmbown/CodeWhale · error · ExecError
durationSec must be positive
Error message
durationSec must be positive
What it means
The record action validates the optional durationSec: when provided, it must be a finite number greater than 0. Null/undefined (unbounded recording) is allowed; NaN, Infinity, zero, negatives, or non-numbers throw before the native recorder is spawned.
Solutions
- Pass a positive finite number, e.g. record({ durationSec: 30 }).
- Use null/undefined for unbounded recording instead of 0.
- Coerce and validate: Number(durationSec) then check Number.isFinite and > 0 before calling.
Example fix
// before
await record({ durationSec: "30" });
// after
await record({ durationSec: 30 }); Defensive patterns
Strategy: validation
Validate before calling
if (durationSec != null && !(Number.isFinite(durationSec) && durationSec > 0))
throw new Error("durationSec must be a positive finite number or null"); Type guard
const isValidDuration = (d) => d == null || (typeof d === "number" && Number.isFinite(d) && d > 0);
Try / catch
try { await record({ durationSec }) } catch (e) {
if (String(e.message).includes("durationSec must be")) {
await record({ durationSec: Number(durationSec) > 0 ? Number(durationSec) : null });
} else throw e;
} Prevention
- Coerce config-driven durations with Number() and validate finiteness before calling.
- Use null for unbounded recording instead of 0.
- Guard computed durations against NaN/Infinity from division.
When it happens
Trigger: Calling record({ durationSec: 0 }) or a negative value; durationSec arriving as a string ('30') or as null-coerced value from config; passing Infinity from a computed timeout.
Common situations: Config files parsed as strings; an agent computing duration via division producing NaN; UI input allowing 0 meaning 'default' while the API expects null for unbounded.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- choose one available display for recording
- element has no resolved accessibility identity
- native screen recorder cannot own its client lifetime…
- open_application needs name, bundle_id or pid
- recording action " " requires id
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/cf184800e706c5da.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:637
// start through the same window_info the AX path uses, so a background
// window records behind the user's work. The rect is fixed at start —
// it does not track later moves or resizes.
let window = null;
if (app_ref !== undefined || window_id != null) {
window = await native("window_info", { app_ref: app_ref === undefined ? state.inputApp ?? undefined : app_ref, window_id });
if (!window?.points || !(window.points.w > 0) || !(window.points.h > 0)) throw new ExecError("the selected application has no capturable window — call list_windows");
if (region) throw new ExecError("choose app_ref or region, not both");
region = [window.points.x, window.points.y, window.points.w, window.points.h];
}
let disp = display ?? state.activeDisplay;
if (window && display == null) {
const cx = region[0] + region[2] / 2, cy = region[1] + region[3] / 2;
const host = displays.find(d => d.points && cx >= d.points.x && cx < d.points.x + d.points.w && cy >= d.points.y && cy < d.points.y + d.points.h);
if (host) disp = host.index;
}
const selected = displays.find(d => d.index === disp);
if (!selected) throw new ExecError("choose one available display for recording");
if (durationSec != null && (!Number.isFinite(durationSec) || durationSec <= 0)) throw new ExecError("durationSec must be positive");
const capabilities = await native("input_capabilities");
if (capabilities?.record_owner_pipe !== 1) throw new ExecError("native screen recorder cannot own its client lifetime; update Computer Use before recording");
const helper = await nativeHelper();
throwIfAborted();
const child = spawn(helper, [JSON.stringify({ tool: "record", args: { file, displayID: selected.id, region, durationSec, owner_pipe: true } })], { stdio: ["pipe", "pipe", "pipe"] });
child.stdin.on("error", () => {});
const startedAt = new Date().toISOString();
let stderr = "", output = "", ready = false;
const completion = new Promise(resolve => {
child.once("error", error => resolve({ code: -1, error: error.message }));
child.once("close", code => resolve({ code, error: stderr.trim() }));
});
child.stderr.on("data", chunk => { stderr = (stderr + chunk).slice(-4000); });
const recording = { child, completion, pid: child.pid, file, startedAt, mode: "ScreenCaptureKit", display: disp };
rec.set(id, recording);
const signal = currentSignal();
let timer, abort;
try {View on GitHub (pinned to 73e0f67d83)