The two kinds of answer
An error means DeckTalk could not run, so nothing was judged. It prints one block and the command stops.fix line under it appears only when the finding carries one.
Under --json, error is filled for the first and null for the second, and findings carries the
second as an array of objects.
An error exits 2 or 3, or 130 when the run was interrupted, and a finding at or above the threshold
exits 1.
Exit codes, Error codes and
Finding codes list all of them in one place.
The three commands to run first
None of the three produces a film and none of them spends.
decktalk check --no-pages goes further
and opens no browser at all, which makes it the one to run on a machine with no Chromium and inside a
commit hook. It names every judgement it could not reach rather than passing quietly.
decktalk check --no-frames keeps the browser and drops the freeze comparison, which is the faster
half when you only want the reading of the files and the page.
Apply the fix a finding carries
A finding may carry afix. It is one of three kinds, and it declares how far it may be trusted.
decktalk check --fix, decktalk doctor --fix and decktalk build --fix apply every safe fix and
print every other one. --no-fix applies none. In the library it is
Project.apply(finding, unsafe=False), and passing unsafe=True is the only way an unsafe fix is
ever written.
The count line tells you before you run it: Found 1 finding, 1 error. 1 fixable with --fix.
A safe fix does the boring part and leaves the judgement to you. check --fix on a project with no
cue file writes every row the deck declares with an empty phrase, so the next check reports a
CUE_UNRESOLVED for each empty phrase and a build stops at cue on them. That is the fix working.
Fill in the phrases and run it again.
Carry on past a code, or change what fails a run
--allow CODE carries on past one code, and --fail-on chooses which severity fails a run.
Which findings fail a run is the table of both.
build also stops on what fails it. When a stage inside a build judges something at the --fail-on
threshold, the build stops after that stage rather than carry the finding into the film, because a
film that is wrong in a way nobody has to watch it to discover is not worth recording. The stages it
did not reach are reported as skipped, and the run still returns everything it knows: it exits 1,
its findings are printed and in the JSON, the money narrate already spent is in cost, and
stopped_at names the stage it stopped after.
TAKE_MISSING lines are warnings, so they do not stop the build. The one error does.
The two flags that decide what fails a run decide what stops a build, because a build that carried
on past a finding its exit fails on would spend on a film already called wrong.
--allow CODEcarries the build past that code, and it works for every code. The finding is still printed and still in the JSON, and it no longer stops the build or counts toward the exit.--fail-on warningstops on a warning as well, and--fail-on neverruns every stage whatever it finds.
verify is the last stage, so its findings end a build rather than stop it. Reach for --allow only
for a code you mean to keep, and say why, because the film it lets through carries that finding.
A cue a deck under construction has not drawn yet is the usual case:
From a symptom to a code
Every code links to its own page, which carries that code’s sentence, its severity and the settings
keys that move it. The rest of this page says what to change.
A missing toolchain
DeckTalk needs Chromium to record a page and ffmpeg with ffprobe to measure, cut and encode. It fetches both the first time a command needs them and keeps them in a per-user cache. What you see.error[TOOL] when a command needed one of them and could not run it, or a
FILE_MISSING finding from doctor for each component this machine has not got.
What to do.
- Run
decktalk doctor. It prints a row per component with its version and where it came from. On a terminal it then offers once to fetch what is missing, and with--no-inputand no--fixit fetches nothing. - Run
decktalk install, ordecktalk doctor --fix, which applies the safecommandfix that eachFILE_MISSINGcarries. Both fetch the toolchain up front rather than in the middle of a build. - On Linux,
decktalk installalso asks Playwright for Chromium’s system libraries, which it installs through sudo, so it may ask for a password. A build never does that, because a build runs unattended and a password prompt is a hang rather than a question. That is why a Chromium that will not launch says to rundecktalk install. - A download that stalls stops with
error[TOOL]saying how much of ffmpeg arrived and that the next run resumes from there. Rundecktalk installagain: it asks for the rest rather than the whole archive, and checks the pinned SHA-256 on all of it. On a link too slow to finish, settools.ffmpegandtools.ffprobeto a build you already have.
tools.ffmpeg and tools.ffprobe name binaries the machine already has.
tools.cache_dir moves what DeckTalk fetches, Chromium and ffmpeg together. tools.chromium records with a browser the
machine already has and skips the Chromium download entirely. All four are machine keys, so set them
with decktalk config set KEY VALUE --scope machine, because a decktalk.toml that sets one is
refused. Set
PLAYWRIGHT_DOWNLOAD_HOST to a mirror when a proxy blocks Playwright’s own host, and set
tools.cache_dir when the home directory is read-only or the browser is shared.
FILE_MISSING is also what a stage raises when a file the project names is not on disk, such as an
audio file of your own that a [[mix.effect]] row cues or a clip a section names. There the fix is
to write the file or correct the path. A sound the score describes and has not bought yet is
SOUND_MISSING instead, a warning that plays silence in its
place, and decktalk score --spend buys it.
An input error
What you see.error[INPUT], naming the file, the key or the argument and what is wrong with it.
The command exits 3 and makes nothing.
What to do. Correct what the message names and run the command again. The hint says where to
look: the line of a decktalk.toml that is not valid TOML, or the sections there are when a
--section names one that is not. A key that belongs to the machine, such as record.concurrency,
is refused in decktalk.toml with this code, and the hint names the
decktalk config set KEY VALUE --scope machine line that moves it. A program this machine does not
allow is also INPUT: see A program the project names.
A refused command line
Exit 2 means DeckTalk refused before doing anything, and a retry of the same line is refused again.USAGE. The parser refused the line. The message names what is wrong with it. Run
decktalk <command> --help, or decktalk schema for the whole instruction set as one JSON object.
APPROVAL. A run that would spend money was not approved. narrate and score are the two
stages that spend. A run with nothing to buy is never asked, so a project whose takes are all on disk
builds with no flag and no key.
--no-input never prompts. It takes the safe default, or it refuses and names the flag that would
have answered, which is what makes a non-interactive run deterministic.
APPROVAL also refuses four steps that are not a spend: init into a directory that holds files
(pass --overwrite), a Chromium install whose system libraries need a password nobody gave, a run
that may spend and would open an untrusted page (voice in a run of its own first), and a
--max-cost that cannot price the run because nothing states the rate. Each hint names the command
that approves it.
In the library there is no terminal to ask, so nothing spends without spend=True, and
ApprovalRequired is raised rather than money being spent.
A stage run before the one it reads
What you see.error[NOT_BUILT], naming the file that is not there and the command that writes it.
decktalk build, which runs every stage in order.
decktalk narrate --no-spend writes the takes without buying anything, with a placeholder wherever a take is missing. decktalk status names the
next command to run.
A voice that did not answer
What you see.error[PROVIDER], with the HTTP status and the host when ElevenLabs answered, or
the reason when it could not be reached.
What to do.
- Read the status in the message. A 401 is a key ElevenLabs does not accept, and a 429 or a 5xx is ElevenLabs being busy, which a later run usually gets past.
- Run the same command again. Each take is written to the takes directory as soon as it is voiced, so the turns already voiced are found on disk and the run pays only for the rest. When the message says the request was possibly charged, its reply broke after it was sent, so check the account’s usage first: running again buys that take once more.
- A key that is not set at all is not this code. It is an
INPUTerror namingELEVENLABS_API_KEY, raised before any request is sent.
A locked project
What you see.error[LOCKED], naming the process and the run that holds the directory, with the
hint to wait for that run or stop it.
One writing run at a time holds an operating system lock on build/.lock. While it holds it,
build/.lock.owner names its process id and its run id. A second writing run is refused rather
than allowed to overwrite the first one’s work.
What to do.
- Find the run named in the message. It is usually a
--watchbuild in another terminal. - Let it finish, or stop it. The system releases the lock the moment the run ends, including when
it is killed. The empty file stays in
build/, and that is expected. - A lock is never left behind by a process that is gone, because the lock belongs to the process
and not to the file. When a killed run left
build/.lock.ownerbehind, the next run takes the lock, prints a warning saying so, and carries on. Deleting the file is never needed. - To read the project while a build is running, use
decktalk statusordecktalk check --no-pages. Neither writes, so neither takes the lock.
narration.timeout_seconds long with the waits between narration.retries. Let the other run finish,
then run this again, and it copies the take in rather than buying it.
An unresolved cue
A cue is one thing written in two files. The page declares a moment with a cue name, andcues.json
carries its cue id, such as 4.1:expand, in its id field. Four codes
say which half is missing.
Reading
CUE_UNRESOLVED. The message names the phrase and how many words the section actually
speaks.
decktalk words --section 1 to see every spoken word with its start and its end, which is the
clock a phrase is written against. Match the phrase to words that are really there. A phrase is
matched without case by default, and the row’s own keys narrow it: occurrence picks which of
several matches to take, case_sensitive makes the match exact, offset_seconds moves the second the cue
fires, and verify set to false leaves the cue out of the measurement.
Reading CUE_STALE. verify measures the film on disk against the cue times the last build
placed. Edit cues.json and run verify alone, and the times no longer match the file:
decktalk build. It places every cue again,
records only the sections whose cue times moved, and keeps the rest. decktalk status names build
as the next step for the same reason.
Reading CUE_UNLISTED. This one carries a safe edit fix that writes a row per declared moment
with an empty phrase for you to fill. On a cues.json laid out in a way DeckTalk cannot place a row
into, the fix is display instead and prints the rows for you to paste.
Reading CUE_UNKNOWN. When DeckTalk can see which cue the row was probably meant to be, it
offers an unsafe rename fix, which is unsafe because renaming an id the author typed is a reading
rather than a fact. --allow CUE_UNKNOWN carries a build past it, which is the usual choice for a
deck under construction that lists the cues of slides nobody has drawn yet.
Which command sees what. The cue stage compares cues.json against the catalog the page
published into its recordings, so a section whose page has never been recorded is left unjudged
rather than guessed at. decktalk check makes the same comparison against a page it opens live,
which is why a fresh project shows CUE_UNLISTED and CUE_UNKNOWN from check and not from cue.
CUE_OVERLAP is the neighbouring warning. It fires when two cues resolve close enough
together that one motion is still playing when the next begins, and the message says how far apart
they landed and how long the first was still running.
A picture that does not change
A cue that resolves to a second means nothing if nothing on screen moves at that second. Three codes measure that, all of them by comparing frames.
What you see. The message carries the measured share of the frame and the floor it is under.
- Look at the two frames the message names.
decktalk checkwrites a frame either side of every cue underbuild/frames/, and draws them all ontobuild/storyboard.html. decktalk storyboard --before 1.1:title --after 1.1:titlefreezes just that pair, which is the fastest way to see what the reveal actually did.- Make the reveal bigger. A reveal that changes a few pixels is a reveal a viewer misses too, so the usual fix is on the page rather than in the settings.
- Check the element really has a moment attribute and that its cue is the one that fired. An
element that only changes class needs
data-class, and a class change that animates needs the page’s own stylesheet to do the animating.
verify.changed_share_min_percent is the floor, as a percentage of the frame.
verify.thin_change_factor is how many times that floor a change must cross to stop being thin.
verify.probe_diff_luma is the per-pixel luma difference that counts a pixel as changed, and
verify.probe_delays_seconds is when after the cue the frames are compared. video.crf moves all
three, because a harder compression smooths away small changes before anything measures them. Read
any of them whole with decktalk config explain verify.changed_share_min_percent.
A recording that opens black or runs short
These two are measured against a recording rather than against a file you wrote.record measures
both on the file it has just written, and verify measures RECORD_BLACK again on the finished
film.
Where the black usually comes from. The recorder steps the section clock from second 0, and
frame
k is exactly what the page shows at second k / fps. A black frame is therefore a page that
showed nothing visible at that second: a slide whose every element waits for a later cue, a
background that is black on purpose, or a page that threw before it drew. There is no start to
guess, so a black frame never moves the reveals around it.
Where a frozen picture usually comes from. Something on the page threw, so whatever it drew
stopped there while the recording carried on. The page reports that itself, through the probe, as
one of these:
What to do.
- Run
decktalk serveand open the page in your own browser. It serves the deck directory and the filesdecktalk.tomldeclares, and nothing else, so what you see is what the recorder sees. A declared file is reached only by the spellingdecktalk.tomlgives it, on every platform, so a page that asks for it with other capitals is refused. Browser preview and recording says what the origin serves. - Read the browser console. These seven codes are the page’s own report from the browser, so the detail behind each one is there.
- Run
decktalk check, which opens the same page through the same probe without recording anything. - For a truncation, record the section again with
decktalk record --section N. A stepped recording holds one frame for every step of the section clock, so a short one is an encode that ended early rather than a page that was slow.
A recording that stopped at its deadline
What you see.error[TOOL] saying a page stopped answering, ran past its deadline, or that its
renderer crashed. The message names the page and what it was doing when it stopped: a load, a step,
a capture, a still or a report. record also names the section and the frame.
Every call into a page is held to record.page_deadline_seconds, 15 seconds by default, which is many
times the longest a step takes, so only a page that stopped answering reaches it. A recording as a
whole is held to record.deadline_slowdown times what this machine’s measured
record.chunk_startup_seconds and record.step_seconds say it should take, 30 times by default, so a
page that answers every call and never gets on still stops.
What to do.
- Look for a script that spins the page’s main thread, or a font or an image that never arrives.
decktalk serveand the browser’s own tools show it. - On a slow or loaded machine, raise
record.page_deadline_secondsorrecord.deadline_slowdownwithdecktalk config set KEY VALUE --scope machine, or for one run withDECKTALK_RECORD_PAGE_DEADLINE_SECONDSandDECKTALK_RECORD_DEADLINE_SLOWDOWN. - A chunk whose renderer crashed is recorded again in a fresh page, up to
record.renderer_retriestimes, once by default, and gives the same frames, because a frame is a function of its index. A renderer is most often killed for memory, so lowerrecord.concurrencywhen the crash repeats.
A late landing
The whole point of DeckTalk is that a picture arrives on its word. These codes are how it says a picture did not.
What to do about
CUE_OFF.
- Read the number in the message. It says how far off the change landed and what the limit is.
- A whole section that is late by the same amount is usually motion that keeps a clock of its own.
The recorder steps every timer, animation frame and animation on the page, but not a Worker’s
clock, so draw that motion from
DeckTalk.framewith the region it draws in instead. A<video>or an<audio>is refused asPAGE_REALTIME_MEDIAbefore it can drift. - One cue that is late on its own is usually a phrase that resolves to the wrong word. Run
decktalk wordsand read what the section really says around it. Setoccurrenceon the row when the phrase repeats. offset_secondson acues.jsonrow moves one cue by a fixed number of seconds. Use it when the picture genuinely belongs slightly before or after its phrase, and not to paper over a phrase that matched the wrong place.verify.cue_offset_max_msis the limit, andverify.av_offset_max_msis the audio against video limit beside it. Widening a limit hides the measurement rather than fixing the film.
Something meant to move for longer than half a second is a motion or a loop rather than a moment.
Declare it, and verify masks it out of every cue and measures its own departure and arrival, as
Things that move shows.
MOTION_OFF, MOTION_NO_EDGE and MOTION_MASK_LARGE
are the codes it raises, and the findings gives the fix for each.
How long a moment may move gives the range of
data-in-seconds and data-stagger and the stagger’s arithmetic, and decktalk schema page prints
every attribute with its range. motion.scale multiplies every declared length, so a value above one
is what pushes a deck that was fine over the ceiling.
CUT_SPEECH and CUT_POP are about the cuts rather than the reveals. A cut that slices a word
means the section’s narration did not have room to finish, and narration.tail_seconds is the
silence kept after the last word of a section so that a cut breathes.
verify.cut_max_dbfs and verify.cut_window_seconds are how quiet, and for how long, a cut has to
be to pass.
CUT_POP is measured only where a section declares seamless = true, which is the promise that its
picture continues the one before it. The last frame of the previous section and the first frame of
this one are compared, and more than verify.cut_change_max_percent of the frame differing means the
promise was not kept. Either make the two pictures actually continue, or drop the promise and let the
boundary be an ordinary cut, which [transition] dips through.
A motion verify cannot measure
What you see. One of the motion codes: something on the page moves that nothing declared, a motion names a cue that never fires, or a declared motion lands away from its word.
Everything that moves for longer than a moment is declared:
data-motion or data-loop in the
markup, DeckTalk.motion from a script, or DeckTalk.frame with the region it draws in. Verify masks
what is declared out of every cue and measures a motion’s own departure and arrival. Footage belongs
in a clip section, and a picture a program draws belongs in a render section.
What verify does with each gives the measurement.
A loudness miss
What you see.MIX_LOUDNESS, which is a warning, with the measured numbers in the message.
The mix is mastered to audio.target_lufs, with audio.true_peak_max_dbtp as the peak ceiling. The
defaults are -16 LUFS and -1.5 dBTP, which are ordinary values for video on the web.
audio.range_max_lu is the loudness range the measurement is made against, and no mix fails
on it. Sound says exactly when MIX_LOUDNESS is raised.
What to do.
-
Check whether the loudness pass ran at all. On a build with placeholder narration it does not,
and the run says so.
Judge loudness on a voiced build.
-
A film that misses the target by a lot usually has a bed fighting the voice.
[mix] music_db,[mix] music_duck_dband[mix] ambience_dbare where that is settled. -
Change the target only when you are mastering for a platform that asks for a different one. Read
it whole first with
decktalk config explain audio.target_lufs, which prints its range and the layer that set the value in force, and whose--jsonlists every layer that stated it.
A page that loads an asset from a network origin
What you see.PAGE_CDN_ASSET, once per other origin the page reached for, naming that origin.
- Download the asset into
deck/and point the page at the local copy. A font, a stylesheet, a script and an image are all the same answer. - For equations there is nothing to download. DeckTalk serves the packaged KaTeX at
/__decktalk/katex/on the page’s own origin, so a page that links those two files loads nothing from a network. - Run
decktalk serveand open the page. That origin serves the deck directory and the filesdecktalk.tomldeclares and nothing else, so anything the page still fetches from elsewhere is visible in the browser’s network panel. - Keep the code if you mean it.
--allow PAGE_CDN_ASSETcarries a run past it, and it is worth writing down why, because a film that cannot be rebuilt in a year is what the code is warning about.
A sandbox that will not start
What you see.error[TOOL]: could not launch Chromium with its sandbox on, on a machine whose
record.page_policy is untrusted.
An untrusted page runs only inside Chromium’s sandbox, and a machine that cannot start it is refused
rather than given a browser without one. The sandbox is a property of the machine, so no edit to the
project clears this.
What to do.
- In a Linux container, run the render as a user other than root, because Chromium refuses its sandbox to root.
- Give that container a seccomp profile that allows user namespaces. Docker’s default profile does not, and a render as an ordinary user under it is still refused.
- If the pages are your own work, the machine does not need the policy.
trustedis the default, anddecktalk config unset record.page_policy --scope machinereturns to it.
untrusted, a request for another origin is refused rather than sent, so a page that loads a
font or a library from a CDN renders without it. The run still names that host as PAGE_CDN_ASSET,
and the fix is the one above: put the file beside the page.
A page someone else wrote says what the policy does.
A program the project names
What you see.PROGRAM_NAMED from decktalk check, once for each program the project names, and
error[INPUT] from build or record, before anything runs, when this machine does not allow it.
A render section’s render_command is a program, and it runs with your user’s rights, so it can
read every file you can, .env included. record.programs is the machine’s to set, and a project
cannot allow its own programs: refuse, the default, runs none of them, ask runs them when a person
at a terminal says yes, and run runs them without asking. Under the untrusted page policy no
program runs whatever it says.
What to do.
- Read the program the finding names before you allow it.
- Allow it for one run with
decktalk build --no-spend --set record.programs=run, or withDECKTALK_RECORD_PROGRAMS=runin the environment. - To be asked each run, run
decktalk config set record.programs ask --scope machine.
A page the contract refuses
These come out whencheck opens the page, and all but PAGE_NO_DESCRIPTION and
PAGE_SWAP_APART also when record does and again when verify reads the recording log. Most of
them the page reports about itself, through the probe, and the rest DeckTalk reads off the catalog
the page published. All of
them are about markup rather than about timing, so all of them can be cleared before a single credit
is spent. One of them, PAGE_SWAP_APART, is a warning, and the rest are errors because the
contract either declares a thing or it does not.
What to do. Run
decktalk schema page, which prints every attribute with their
values, their defaults, their ranges and their spans. It is generated from contract.ts, the one
home of every attribute, so it cannot disagree with what the runtime does. Then fix the markup.
Two of these are about the transcript rather than the picture. PAGE_NO_DESCRIPTION and
PAGE_CLASS_UNDESCRIBED mean a reader of the transcript loses a step the viewer saw, and
data-describe with an empty value is how you say an element is decorative and meant it.
PAGE_CLASS_NOT_REDUCED is about reduced motion. No flag duplicates a settings key, so reduced
motion is --set motion.reduce=true on any command that runs against the project, and it asks
Chromium for prefers-reduced-motion. The page’s own stylesheet is what has to honour that query,
and this code says a class it adds still animates when nobody asked it to.
PAGE_CLASS_UNDESCRIBED is the other half of data-class. A class change is a change to the
picture, so data-describe-class gives each one a description, written as cue and description pairs
separated by a vertical bar, and the transcript prints it.
A script that is not ready to be voiced
All three come from
decktalk check reading script.md, so all three are free to find and all
three are found before a credit is spent. This is the cheapest check there is, and running it in a
commit hook with --no-pages costs nothing.
Run it before you voice anything. narrate refuses a comment, a brace or a bracket inside a
paragraph, and it lists every such line at once. It does not refuse an unfilled blank such as
[NUMBER], so a voiced run reads it out. Only check reports that.
SCRIPT_SPOKEN_SYMBOL is a warning because sometimes you do mean the symbol. Write out what the voice
must say, and Writing for the ear is the guide for it.
SCRIPT_PAUSE_DROPPED names a section and a voice whose turns hold a [pause N] on a model that
renders no timed pause, once per section and voice, and narrate refuses to buy a take that would
lose the pause. Move to a model that renders one, or
write a [beat], which every model reads. script.md names the
models.
A voice direction that names no voice
Every voice a script hands a turn to needs a
[voices.NAME] table, and in a section that names a
voice, every turn opens with a [voice NAME] line. A line holding only a note that is exactly a
voice’s name, such as [guest], plays as a beat and hands nothing over. All four come from
decktalk check reading script.md against decktalk.toml, so decktalk check --no-pages finds them
before anything is bought. Write a dialogue shows each.
A take that is missing
A run with
--no-spend plays every take on disk and puts a placeholder in each turn whose take is
missing, which is one TAKE_MISSING per turn, and one per section when one voice reads it. It is a
warning because the film is still whole and only that turn’s timing is estimated, so a draft build exits 0. Pass --fail-on warning to make a
missing take fail a run.
A take is found by its text, its voice, its model and its voice settings, so a turn reports it after any of
those changed, and on a fresh clone whose takes were never committed. Without a voice id no voiced take
can be matched at all, and the run says so in one line before the findings. Set [voices.narrator] id in
decktalk.toml, which is a published name rather than a secret and is committed with the project,
and the takes on disk play again.
A take that plays silence or cuts a word
These three come fromnarrate, on the run that makes the take, and each names the turn.
A take is cut at its sound end, the point after which it stays under
narration.sound_end_noise_dbfs for at least narration.sound_end_min_run_seconds. A take with
no sound above that level plays its turn as silence, and a word placed after it is never heard. A
take whose voice sent no word times is timed by the aligner, and one no aligner timed plays
estimated words. Voice one turn again buys a
single turn.
When the answer is not a code
Some things are not judgements. Use the stream.--eventsprints one JSON line per event on stderr as it happens, and every run also appendsbuild/events/<run>.jsonlwhether you passed it or not. The event stream names every line and its fields.stage.doneandsection.donecarry anoutcome, so a run that stopped early still says what it did not reach rather than leaving you to infer it from a shorter list.events.keep_runsprunes the oldest files, and its default is 20.-vputs debug lines on stderr and prints the traceback of a bug.error[INTERNAL]means a bug in DeckTalk, and-vis what turns it into something worth reporting.error[CANCELLED], exit 130, means the run was stopped, by Ctrl-C or by the caller’scancel. Every turn voiced before the stop is on disk, so running the command again buys only the rest.decktalk statusreports where the project stands and what to run next.
Related
- Every command, flag and code in one place: CLI
- Every settings key with its range and its layer: Configuration
- What a page may declare: The page contract
- What
verifymeasures and how: Verify - The questions before the first build: FAQ