RecordingCAUTION
A framework for building agentic apps
Overview
A framework for building agentic apps
d31edd75948aOBSERVED · 2026-10-14What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
---
name: recording
description: >-
How screen and camera recording works in Clips — MediaRecorder lifecycle,
chunked upload, permission handling, pause/resume, camera bubble overlay,
error recovery, and screenshots (still-image recordings). Use when adding
or modifying the recorder UI, the upload endpoint, permission prompts, or
anything that handles a screenshot.
---
# Recording
## When to use
Reach for this skill any time you touch the recorder: the record button, the in-progress toolbar, permission prompts, chunked upload flow, or the camera bubble. If you're adding support for a new source (e.g. tab capture, iPhone continuity camera) or changing how chunks are finalized server-side, this is your map.
## Data model touched
- **`recordings`** — the row gets created as soon as the user presses Record or imports a source. Native/file recordings transition `uploading` → `processing` → `ready` (or `failed`). `videoUrl`, `durationMs`, `videoSizeBytes`, `width`, `height`, `hasAudio`, `hasCamera` are populated as the upload streams in. Loom imports use `import-loom-recording` and become ready with either a Clips-hosted MP4 or a Loom embed when MP4 export is unavailable.
- **`application_state.record-intent`** — the agent writes this when it wants to start a recording. The UI reads and clears it, then prompts for permission.
- **`application_state.navigation`** — set to `{ view: "record" }` while the recorder is active.
Binary uploads hit the **custom API** routes (`/api/uploads/:id/chunk` and `/api/uploads/:id/abort`) rather than actions, because actions aren't the right tool for binary streaming bodies. The final chunk calls `finalize-recording`. Loom URL imports are metadata-only and should go through the `import-loom-recording` action.
Some recordings are linked to a meeting — when `meeting_id` is non-null on the recording row, it was created via `start-meeting-recording` and both the `recording` and `meetings` skills apply. See the `meetings` skill for the bidirectional link.
## Lifecycle
1. **Intent.** Either the user clicks Record (global `Cmd+Shift+L`) or the agent calls `pnpm action start-recording --mode=screen`. The agent version writes `record-intent` to application state; the UI picks it up and initiates the same flow as a user click.
2. **Permission.** Call `navigator.mediaDevices.getDisplayMedia({ video, audio })` for screen, `getUserMedia({ video, audio })` for camera. Do **not** prompt without a user gesture. The agent path relies on the UI's button — we never bypass the browser's permission model.
3. **Create row.** As soon as the stream is granted and storage is connected, call `create-recording` to insert the row with `status: "uploading"` and a pre-generated id. That id is used for every subsequent chunk upload. **Recording never waits on storage:** with none connected (or when `create-recording` refuses), the web recorder records under a local id with no row, and asks for storage after Stop; the desktop app records to a file in Movies/Clips (also while the storage check is still pending) and, once storage connects, queues that file into its pending-upload list (`queueRecordFirstUpload`), with an **Upload now** banner until then; the extension opens the web recorder tab.
When the web recorder stops with no storage, keep the local copy available in the pending view with an in-browser preview and **Download a copy** while offering Builder/S3 for durable upload. Previewing or downloading must not delete the local copy.
4. **Record.** Start a `MediaRecorder` with `mimeType: "video/webm;codecs=vp9,opus"` (fallback to vp8, then browser default). Use `timeslice: 2000` so chunks arrive every 2s.
5. **Upload each chunk.** `ondataavailable` streams bytes to `/api/uploads/chunk` with headers `X-Recording-Id` and `X-Chunk-Index`, while independently mirroring every chunk to the `IndexedDB` local copy (`app/lib/recording-backup.ts`). The copy has a state (`recording` → `recorded-local` → `uploading` → `uploaded`) and is deleted only when the server reads the clip `ready`. Closing or reloading the tab releases capture but never discards the copy (the row is marked `recording_interrupted`); every app load offers **Finish upload** for copies no live tab owns (Web Locks), which uploads them as a fresh clip (`app/lib/local-recording-upload.ts`) and trashes the superseded rows. A full disk warns once and keeps the in-memory copy for upload or Download. Each copy is stamped with the account signed in at Stop and uploads only into that account (`create-recording` re-checks `expectedOwnerEmail`); an ownerless copy needs an explicit **Upload to my account** claim, and another account's copy stays in the browser for its owner. Nothing deletes a copy without a confirmation that says it will be deleted. A copy with no row yet is invisible to the agent: point the user to **Finish upload** in Clips, never to re-record.
6. **Live transcription.** Alongside the MediaRecorder, `useLiveTranscription` runs the Web Speech API to accumulate transcript text in real time. On stop, the client calls `save-browser-transcript` to persist the result immediately — no API key needed. Desktop recordings use local Whisper/macOS speech first when available, and fall back to Web Speech in the webview on non-mac before relying on upload transcription.
7. **Finalize.** On stop, send the final chunk to `/api/uploads/:id/chunk?isFinal=1`. The route calls `finalize-recording`, which stitches chunks, makes the media seekable (see below), uploads the finished media when storage is configured, transitions `status` to `ready`, then kicks off `request-transcript` for higher-quality output (see `ai-video-tools`). With no storage connected it parks the row as `waiting_storage` for up to seven days (`WAITING_STORAGE_LEASE_MS`); a finalize after storage connects claims a normal one-hour lease, and past the seven days the upload reaper fails it as `storage_setup_required` and deletes its scratch chunks.
8. **NavTrust audit
CAUTIONgrade B · trust 89/100 Install with care. The audit found things worth knowing before you trust its output.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | WARN |
| L1 | Static analysis of the code | NA |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (5)
.agents/skills/an
.agents/skills/content-product-development
.agents/skills/design-exploration
.agents/skills/turn-into-app
.agents/skills/visual-edit
Gates applied: no_behavioural_pass.
d31edd75948afull audit observations/trust-audit/skill/builderio__recording.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-14 | d31edd75948a | CAUTION | B | 89 | first audit |
Questions
What does the Recording skill do?
A framework for building agentic apps
Is Recording safe to install?
With care. The audit graded it B (89/100) and found 5 things worth knowing before you trust this skill, listed below with the exact line each was found on.
What can Recording access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
How current is this page?
The grade is for one exact copy of the source (d31edd75948a), read on 2026-10-14. The repository is watched, and a new audit runs when it changes — this is the first audit.
Add this badge to your README
Shows this page's audit result and links back to it. It updates when the audit does.
[](https://modelsatlas.com/skills/builderio/recording/)
Not your listing, or something out of date? Claim or update your listing.