Skip to content

A hook that fails

A hook is a program you wired in. When one stops a build the message names it, and says where the fault is: in a block you wrote, in the hook, or in how it was wired.

What it looks like

ERROR: hook 'diagrams' did not answer 'describe' (died, refused, or past the 30s
  timeout)
  │ connect: No such file or directory
  See https://keystone.knight-owl.dev/errors/hook-failed/#it-did-not-answer
ERROR: hook 'diagrams' on a 'mermaid' block starting "flowchart LR A --> B":
  syntax error on line 3: expected an arrow
ERROR: hook 'diagrams' on a 'mermaid' block starting "flowchart LR A --> B"
  returned no body
  See https://keystone.knight-owl.dev/errors/hook-failed/#its-reply-was-unusable
WARN: hook 'diagrams': no serif font is installed
  Diagrams letter in the fallback font.
ERROR: hook 'diagrams': DIAGRAM_THEME is 'midnight', which is not a theme this
  renders
  Valid: dark, light, neutral
  Set it to one of those, or leave it empty.
ERROR: hook 'diagrams' sent a diagnostic Keystone cannot read
  │ {"message":"the renderer is not installed","severity":"error"}
  A diagnostic names the pieces of a message; Keystone assembles them.
  Name the pieces of your message, with a severity of 'warning' or 'error'
  beside them.
  See https://keystone.knight-owl.dev/hooks/writing-a-hook/#writing-the-message

What it means

It did not answer

Something is listening and did not reply — a hook that crashed after binding its socket, or one still working past the timeout.

A socket with nothing behind it is the first of those. The file outlives the process that bound it for as long as some container holds the volume, so a hook that died inside a container still running looks like one that is there.

A renderer that never started at all produces no message: with no socket to find, Keystone runs as though no hook were wired, and every claimed fence comes out as a code listing. See Nothing was rendered below.

Clear the socket, and check that the build waits

make down clears one: the volume it lives in empties with the containers, and the cache stays where it is. A rebuild alone will not, while whatever holds the socket is still up. For a renderer that is only slow, raise KEYSTONE_HOOKS_TIMEOUT in project.conf. For one that is not ready in time, see Wiring one in.

It disagrees with the engine

speaks …; this engine speaks … means the hook talks a protocol version this Keystone does not. needs Keystone … means the hook set a floor your engine is below. claims no languages means it answered without saying what it renders.

Match the two versions

Upgrade Keystone to the floor the hook asks for, or run a hook release built for the engine you have. claims no languages is the hook's own defect.

It cannot run as configured

hook 'name': followed by the hook's own words is the hook reporting on the settings it was given, before any block was read — a value it does not accept, or something it needs and has not got. The colon is the tell: every message Keystone writes about a hook reads hook 'name' sent …, claims …, speaks ….

What it names is in your project's configuration, not your manuscript. Everything one hook has to say is reported together, so a misconfigured project is not fixed one build at a time.

A WARN: line from a hook is the same report at a lower severity: worth knowing, and the build goes on — unless strict builds are on, which promote it like any other warning.

Fix the setting it names

The hook quotes the value it cannot use. Your settings for it are in project.conf.

It refused the block

A message naming a block, then the hook's own account of it, is the hook saying it could not render that block and why — a syntax error, a directive it does not know. The block is named by its opening, so one fence of twenty is identifiable. Lines behind a │ bar came from somewhere else again — the renderer the hook drives, or the socket it answers on.

Fix the block it names

This one is yours: the message carries the block's opening and the hook's own reason. Nothing in the wiring is at fault.

Its reply was unusable

Keystone refuses a reply rather than build a book around half of it.

From a transform: returned no body, returned assets that are not a list, returned two assets named …, returned asset N with no …, a name that is not a filename, or an asset sent with no decodable bytes.

From a describe: an identity that is not a non-empty string, diagnostics that are not a list, or a format group that names a format twice or puts one in two format groups.

A diagnostic Keystone cannot read is either message in a shape it does not know: a key that is not one of the pieces a diagnostic is made of, a value of the wrong kind, nothing to report, or a severity that is neither warning nor error. The object is quoted under the refusal, so the hook's words survive the shape they arrived in.

This one is the hook's to fix

Writing a hook sets out what a reply must carry.

The cache rather than the hook

Your compose file mounts the cache for the engine, so a path message means you pointed KEYSTONE_HOOKS_CACHE somewhere else.

  • which could not be created and which could not be written — a path the container cannot make, or can reach and not write. Either needs a mount.
  • could not write the hook cache at … — a directory that exists and cannot be written: the wrong owner, a :ro mount, or a compose file whose cache volume is gone.
  • must be a whole number of megabytes and must be a whole number of seconds — KEYSTONE_HOOKS_CACHE_MAX and KEYSTONE_HOOKS_TIMEOUT mean what they say.

Unset it, or mount the path you chose

Unset, the cache is the volume your compose file already declares, which needs nothing of yours — restoring .docker/docker-compose.yaml from git, or from a fresh copy of your template, puts that volume back. That file is the engine's to own, so nothing of yours goes with it. A path of your own needs a mount you add.

Nothing was rendered

No error, a book that built, and every claimed fence in it rendered as a code listing. Keystone looks for a hook's socket once, before the build starts, and finding none it builds exactly as a template with no hook wired would. Nothing failed, so nothing is reported.

The build did not wait for the hook. A template that ships one — as core-diagrams does — carries depends_on with condition: service_healthy, and a hook that never becomes healthy stops the build with a message naming the container. A hook wired in without that wait is the case with no message: the engine starts first, finds no socket, and carries on.

Make the build wait for the hook

Carry depends_on with condition: service_healthy — see Wiring one in.

  • Hooks — what a hook is and how one is wired in.
  • Mistakes Keystone can't catch — a hook that answers with something plausible but wrong is not something Keystone can detect.