Hmbown/CodeWhale · error · ExecError
ffmpeg mux failed
Error message
ffmpeg mux failed: ${(r.stderr || "").slice(0, 300)} What it means
After frames are captured, `recordingStop` invokes ffmpeg to mux the snapshot-series JPEGs into an mp4 (libx264, yuv420p, up to 180s). If ffmpeg exits non-zero, the backend throws with the first 300 bytes of ffmpeg's stderr and passes the raw result along, so the failure cause comes from ffmpeg itself (bad frames, unsupported codec build, disk full, etc.).
Solutions
- Read the stderr excerpt in the error message to identify the ffmpeg failure and address it directly.
- Verify the installed ffmpeg supports libx264 (`ffmpeg -encoders | grep libx264`) and reinstall a full build if not.
- Check disk space and write permissions on the recordings directory (CODEWHALE_CU_RECORDINGS_DIR or ~/.codewhale-cu/recordings).
- Retry the recording with a smaller intervalMs and confirm frames are being captured (recordingStatus shows frames count).
Example fix
// before
// error surfaced: ffmpeg mux failed: Unknown encoder 'libx264'
// after
// install a full ffmpeg build, e.g.: apt-get install -y ffmpeg (includes libx264)
const result = await backend.recordingStop({ id }); Defensive patterns
Strategy: try-catch
Validate before calling
import { execFileSync } from "node:child_process";
const enc = execFileSync("ffmpeg", ["-hide_banner", "-encoders"], { encoding: "utf8" });
if (!enc.includes("libx264")) throw new Error("ffmpeg lacks libx264"); Type guard
null
Try / catch
try { await backend.recordingStop({ id }); }
catch (e) {
if (String(e.message).startsWith("ffmpeg mux failed")) {
console.error("ffmpeg stderr:", String(e.message).slice("ffmpeg mux failed: ".length));
// inspect disk space, codec support, frame files, then retry recording
}
throw e;
} Prevention
- Verify ffmpeg has libx264 before recording workflows
- Monitor disk space in the recordings directory
- Ensure recordings dir (CODEWHALE_CU_RECORDINGS_DIR) is writable
- Keep intervalMs within sane bounds so fps is valid
When it happens
Trigger: Calling `recordingStop({ id })` where the captured frame sequence cannot be encoded: zero/corrupt frames, an ffmpeg build without libx264, disk full or unwritable output dir, or ffmpeg timing out (backends/harmonyos.mjs:315).
Common situations: Recording a session where the screen never produced a frame; minimal ffmpeg builds lacking libx264; recordings directory on a read-only mount; intervalMs so high that fps rounds to an invalid value.
Related errors
- ffmpeg is required on the host to mux harmony…
- choose one available display for recording
- durationSec must be positive
- git not found on PATH
- Harmony recording closed during startup
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/3d2783e8891da36c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/harmonyos.mjs:315
await tick();
throwIfAborted(signal);
if (rec.stopped) throw new ExecError("Harmony recording closed during startup");
rec.timer = setInterval(tick, Math.max(150, intervalMs));
} catch (err) { await stopFrames(); throw err; }
finally { signal?.removeEventListener("abort", abort); }
return { id, mode: "snapshot-series", intervalMs, startedAt, note: "no HarmonyOS CLI screen recorder; frames are muxed into mp4 on stop" };
},
recordingStop: async ({ id }) => {
if (!recording || recording.id !== id) throw new ExecError(`unknown recording "${id}"`);
const { dir, seq, startedAt, intervalMs } = await stopFrames();
const dirOut = process.env.CODEWHALE_CU_RECORDINGS_DIR || path.join(os.homedir(), ".codewhale-cu", "recordings");
fs.mkdirSync(dirOut, { recursive: true });
const out = path.join(dirOut, `rec-${id}.mp4`);
const fps = Math.max(1, Math.min(15, Math.round(1000 / Math.max(150, intervalMs))));
const r = await run("ffmpeg", ["-y", "-loglevel", "error", "-framerate", String(fps), "-i", path.join(dir, "f%05d.jpeg"), "-c:v", "libx264", "-pix_fmt", "yuv420p", out], { timeoutMs: 180_000 });
const bytes = fs.existsSync(out) ? fs.statSync(out).size : 0;
try { fs.rmSync(dir, { recursive: true, force: true }); } catch {}
if (r.code !== 0) throw new ExecError(`ffmpeg mux failed: ${(r.stderr || "").slice(0, 300)}`, r);
return { id, mode: "snapshot-series", frames: seq, fps, file: out, bytes, startedAt, stoppedAt: new Date().toISOString() };
},
recordingStatus: ({ id }) => recording && recording.id === id
? { id, running: true, mode: "snapshot-series", frames: recording.seq, startedAt: recording.startedAt }
: { id, running: false },
recordingList: async () => {
const dir = process.env.CODEWHALE_CU_RECORDINGS_DIR || path.join(os.homedir(), ".codewhale-cu", "recordings");
const out = fs.existsSync(dir) ? fs.readdirSync(dir).filter((f) => /\.(mp4|mov|jpeg|png)$/i.test(f)).map((f) => {
const st = fs.statSync(path.join(dir, f));
return { file: path.join(dir, f), bytes: st.size, modifiedAt: st.mtime.toISOString() };
}).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, 50) : [];
return { dir, recordings: out, running: recording ? [recording.id] : [] };
},
};
}
export default { create };
View on GitHub (pinned to 73e0f67d83)