Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Weft The book

What the compiler refuses

Every compiler finding carries a message saying what went wrong, usually with the fix, and a slug naming the rule. The editor shows the slug beside the message, and weft validate puts it under code. The slug stays the same when the wording of a message changes, so look yours up here.

Everything here is checked when you build, except rule-runtime, which is only checked by weft validate and by the editor just before you Run, Activate or Resync, so a half-wired sketch whose provider has no model named yet still builds while you work on it.

A row marked (warning) is reported and the build goes on, unless the row says otherwise. Every row with no mark is an error, except rule-structural and rule-runtime, which take whatever severity the node’s own rule sets: error, warning, info or hint.

Before the rest can run

SlugWhat it foundWhat to do
parseThe source did not parseRead the message; it names the span and the syntax that failed
enrichA node’s ports could not be worked out: a bad type, or a custom port on a node that does not take themFix the port declaration on the node named
unknown-type (warning)A node names a type the catalog does not have. A warning in the editor; when you build, in weft validate, and in the editor’s check before you Run, Activate or Resync, the same node is an enrich errorCheck the spelling, or add the node under nodes/, or next to the module that uses it under src/. If the node is there but failed to load, or its folder has a metadata.json and no mod.rs, the message says so and why

parse and enrich are buckets: they carry the underlying message rather than a slug per cause.

Wires

SlugWhat it foundWhat to do
unknown-source-nodeA wire reads from a node that does not exist, or a metadata port’s value names oneFix the name
unknown-target-nodeA wire writes to a node that does not existFix the name
unknown-source-portA wire reads a port the source does not declare, or a metadata port’s value names a metadata port its member does not haveFix it; the message lists the real ports
unknown-target-portA wire writes a port the target does not declareSame
duplicate-input-portTwo wires land on one inputRemove one; the message names the first one’s line
double-driven-portAn input is both wired and written into the body, or takes a written value and a metadata wire, or two metadata wiresRemove one of the two
scope-reachabilityA wire crosses a group or loop boundary without going through its portsRoute it through the group’s own ports
deref-pathThe dotted path on a wire cannot be read off the source’s type, or, on a metadata wire, the value written does not hold that keyFix the path, or change the source type or the value
type-mismatchThe source’s type does not fit the target’s, or a metadata port’s value puts ... before a value that is no listChange one side, or cast. The message suggests a cast where one exists
unresolved-typevarA connected port still has a type variable nothing pinned downConnect it to something concrete, or declare the type
type-boundA type variable limited to some types got another one: a String into a group declared Group[T: Media], or into KeepFile, which takes only a fileConnect a value of a type the limit takes; the message names the limit and what you connected
cast-not-allowedA cast node’s two ports are a pair the conversion table does not allowPick types the table allows
graph-cycleThe wires form a cycleIterate with a Loop, or exchange feedback over a bus. If what goes back is a value you can write in the program, like a tool’s description, make it a metadata port
required-port-unmetA required input has no wire, no written value and no default. On a group, loop or included file: a port nothing outside connects, whose value a node inside cannot run without (a required input, or the last possible member of a @require_one_of set)Give it one of the three; for a group port, connect or write it from outside
optional-pass-through-portA ? on a port of a group or an included file, or on a loop port the loop neither iterates nor carries. Such a port passes what reaches it to the nodes inside, so it is never optional itself (a group input passes through)Remove the ?; to let a node inside run without the value, write the ? on that node’s input

Values you wrote

SlugWhat it foundWhat to do
config-type-mismatchA written value does not fit the input’s type, including one a metadata wire writes there, or a metadata port’s value does not fit the port’s own typeFix the value, or the declared type
input-acceptsThe port does not take this kind of driver: a wire on a literal-only port, a written value on a wire-only portUse the one it takes; the message lists them
gate-not-booleanA written _should_flow or _should_not_flow is not a BooleanWrite true or false, or wire it
literal-out-of-rangeA written number is outside the input’s min and max, or off its whole-number step, or a written date on a datetime input is not a moment with its zone (2026-09-03T11:00:00+02:00)Write a value inside the declared range, or the date with its zone
literal-not-an-optionA written value is not one of the input’s dropdown options (for a multiselect, one item is not). A dropdown marked freeText never raises itWrite one of the options; the message lists them
secret-literalA value written in the program is a secret’s marker. A secret is made by a node while the program runs, so a program cannot write oneWire the value from a node’s Secret output
value-on-outputYou wrote a value for an output portRemove it. Outputs are filled by the node, and you read them with node.port
undeclared-port-no-customA key names no input, on a node that does not take added inputsFix the key, or use a node with canAddInputPorts
config-null-literal (warning)A key is written key: null, which does nothingLeave the key out and let the default apply
require-one-of-unknown-port@require_one_of(...) names a port the node does not haveFix it; the message lists the real inputs
require-one-of-unmetNone of the ports in a @require_one_of group is drivenDrive at least one
placeholder-not-an-inputA query or template you wrote names a placeholder ($userId in a Postgres query, {{name}} in a Format template) that is not an input of the nodeDeclare the input (PostgresExecuteQuery(userId: String)), or fix the placeholder; the message suggests a close input, lists the inputs you added, or, if you added none, shows how to declare one
input-not-a-placeholderYou added an input to a node that takes a query or template, and the text never uses itUse it in the text, or drop the input

Names and shape

SlugWhat it foundWhat to do
duplicate-node-idTwo nodes share an id once the graph is flattenedRename one; the message names the first one’s line
duplicate-portA node has two ports of the same name on one side, or a port the program adds to a node has the name of one of its node type’s metadata portsRename one
unpaired-portOn a node that pairs each added input with an output of its name, like Dispatch, one of three things. An added output has no input of its name: add the input, or remove the output. Nothing reads an added output, so what it gets would be dropped: wire it to what handles it. An added input is named after one of the node’s own outputs: rename itThe message says which of the three
reserved-nameA node or group is named after a node type, so name.port would read as an inline nodeRename it
two-gatesA node carries both _should_flow and _should_not_flowKeep the one that says what you mean
run-undecidedEvery input wired into a node is declared with ?, so nothing says when it runsGate it with _should_flow, drop the ? from the input it cannot do without, or add @require_one_of
level-too-large (warning)One level of the graph holds more than fifteen itemsGroup the nodes that cooperate on one job. Nesting a group costs nothing at run time
not-camel-case (warning)A name you chose in the program is not camelCaseRename it to camelCase. The message gives the spelling when it can work one out. For which names are checked, go and read Spelling a name

Ports that come from a list

For a node whose ports are built from entries you write, like a form’s fields or a switch’s cases.

SlugWhat it foundWhat to do
config-ports-not-a-listThe key holding those entries is not a listWrite it as a list
config-entry-not-an-objectOne entry is not an objectWrite it as an object
unknown-config-entry-kindAn entry’s kind is not one this node offersUse one the message lists
config-entry-without-a-portAn entry does not say which port it addsAdd the key naming the port
unknown-config-entry-keyAn entry carries a key that kind does not takeRemove it, or use one the message lists
config-entry-bad-valueAn entry’s value is the wrong shape, or a value the matched input could never holdFill in a value of the right shape
config-entry-missing-valueAn entry is missing a value that kind requiresAdd the key the message names
duplicate-catch-allTwo entries match anything, so the second is unreachableKeep one
catch-all-not-lastEntries sit after a catch-all, where nothing can reach themMove the catch-all last

Metadata ports

For what a metadata port is, go and read metadata ports.

SlugWhat it foundWhat to do
metadata-port-not-writtenA group’s metadata port has no valueWrite it in the body (self.tool = { ... }), or take it from another metadata port
metadata-value-not-literalA metadata port’s value names a node’s output, which only exists once the program runsWrite the value itself, or take it from another metadata port
metadata-reference-cycleMetadata ports take their value from each other, so none has oneWrite the value on one of them
metadata-port-as-targetA wire goes into a metadata port from outsideWire from it instead (other.input = g.tool)
metadata-port-read-insideA node inside a group is wired from that group’s own metadata port (self.tool). Like an output, a metadata port is read from outside its groupRead it from outside the group, or take the value inside from where the port gets it
metadata-into-outputA metadata port’s value goes into an ordinary output of a groupMake that output a metadata port too (-> (*out: Type))

Loops

SlugWhat it foundWhat to do
loop-unknown-config-fieldA key that is not parallel, over, carry, maxIters or trimOnMismatchFix the name
loop-parallel-not-booleanparallel is not true or falseWrite one of them
loop-max-iters-not-integermaxIters is not a whole number of zero or moreWrite one
loop-output-unwrittenA loop declares an output nothing inside it writes: a gathered one would be a list of nothing but nulls, a carried one would never changeWrite self.<output> inside the loop, or remove the port
loop-trim-not-booleantrimOnMismatch is not a booleanWrite true or false
loop-over-unknown-portover names an input that does not existFix the name
over-not-a-listAn over port is neither List[T] nor Generator[T]Declare it as one
loop-carry-unknown-portcarry names an output that does not existFix the name
carry-port-type-mismatchA carry port’s two sides have different typesMake them the same
over-and-carry-overlapA port is in both over and carryPick one role
parallel-with-carryparallel: true with carry ports. Carrying means each turn feeds the next, which is sequentialDrop the carry, or drop the parallel
parallel-without-overparallel: true with nothing to spread outGive it a non-empty over
parallel-with-doneA parallel loop writes self.doneRemove it, or make the loop sequential
loop-unbounded-no-terminationA sequential loop with no over, no maxIters, and no self.done. Nothing would ever stop itIterate a list, cap the count, or wire self.done
gather-output-must-be-nullableA gather output is not List[T | Null]. An iteration that failed leaves an empty slotDeclare it List[T | Null]
reserved-port-nameA loop boundary declares index or done, which the language ownsRename the port
loop-boundary-unpairedA LoopIn without its LoopOut, or the reverseThis one is a compiler bug, not yours. Please report it
loop-config-missing-parallelThe loop’s config did not come through loweringAlso a compiler bug. Please report it

Streams

SlugWhat it foundWhat to do
generator-in-containerGenerator[T] nested inside a list, dict, record or unionA stream is a port type. Declare it at the top level of the port
generator-input-must-be-requiredA stream input is optional or has a default. A stream has no empty valueMark it required and drop the default
generator-multiple-consumersA stream output feeds more than one input. A stream has one takerWire it to exactly one. To broadcast, use a Bus
generator-through-groupA stream crosses a group or loop boundaryKeep the producer and the consumer in the same scope. A loop takes a stream only through its over
generator-into-generic-portA stream is wired into a port whose type is unresolved and not a streamDeclare the target Generator[T], or consume the stream and emit values
generator-not-iteratedA loop takes a stream that is not in over. A stream cannot broadcast into a bodyAdd it to over
generator-not-carriableA carry port is a stream. A stream is a live edge and cannot cross iterationsCarry a value instead
over-stream-not-aloneA stream in over beside other over portsMake it the only one, and zip upstream if you need more

Triggers, infrastructure and addresses

SlugWhat it foundWhat to do
run-settingsA trigger’s durable, recorded or outlivesCaller is not true or false, or it is durable or outlives its caller and is not recordedWrite true or false; turn recorded back on, or the other one off
trigger-into-triggerA trigger is wired into another triggerRemove the wire
trigger-into-infraAn infra node sits downstream of a triggerRemove the wire
trigger-in-loopA trigger inside a loop bodyMove it out of the loop
infra-in-loopAn infra node inside a loop bodyMove it out of the loop
route-path-missingA node claiming a public address has none, or an empty oneGive it a path, like hooks/stripe or chat/{room}
route-path-invalidThe path is not a servable patternThe message says what is wrong
route-method-unknownThat is not an HTTP methodFix it
route-capture-shadows-outputA path captures a name that is also one of the node’s own outputs (cards/{body} on a Route), so the capture could never be readRename the capture
route-output-not-a-captureAn output added to a Route names no capture of its path, or is not typed StringAdd {name} to the path or remove the port, and type every capture String
route-overlapTwo nodes claim addresses one call could reach, and neither is more specificMake one spell out what the other captures, or change a path or a method

For why a trigger cannot be wired into another trigger or have an infra node after it, go and read what a trigger cannot do. For why neither a trigger nor an infra node can sit inside a loop, go and read what cannot go in a loop.

Instances

SlugWhat it foundWhat to do
per-instance-ineligible@per_instance on a node that runs no container of its own. Only an infra node can carry itRemove it; for a value each instance gets (its connection included), write @instance_filled on the field
instance-filled-wiredAn @instance_filled field also has a wire into itRemove the wire, or the marker
instance-filled-place-typesAn @instance_filled field of a node in an included file takes a different type at each place the file is included, and one instance’s value cannot be bothDeclare the field’s type on the node
instance-filled-secretAn @instance_filled field holds a Secret. An instance’s value comes from outside every run, and a secret is made by a node inside oneRemove the marker and wire the field from a node’s Secret output
instance-filled-boundary@instance_filled on a group’s or included file’s own portWrite it on the input of the node inside that reads the value
instance-filled-not-an-input@instance_filled on a key that is not one of the node’s inputsPut it on an input
shared-trigger-reaches-per-instanceA trigger every instance shares (so its runs are for no instance) starts runs that reach something per instance, so every fire would be refused. A route or a socket is not checked: each call can name its instanceMake the trigger per instance too (wire it from what is per instance, or write one of its fields @instance_filled), or keep what is per instance out of what it runs

For what @per_instance and @instance_filled do, go and read Programs with instances.

Types you named

SlugWhat it foundWhat to do
named-type-conflictA named type appears with two different bodiesMake every use of the name carry the same body

Rules a node declared for itself

SlugWhat it foundWhat to do
rule-structuralA rule the node’s own author wrote, checked on every buildWhatever its message says
rule-runtimeThe same, but only checked at the moments listed at the top of this pageFill what the message names. A node with no connection picked is not one of these: the install refuses the run when it starts, and the message gives the command that picks one, weft connect <node>

A rule whose answer depends on a value that arrives on a wire cannot be decided here, so it is checked again when the node runs, on the value that arrived, and fails the node with the same message. For how, go and read rules on a value that arrives on a wire.

Things that have no slug

Some refusals happen before the compiler gets a graph at all, so there is nothing to look up.

A broken metadata.json fails when the catalog loads, with the path and a sentence. A fire payload that does not match a trigger’s firesWith is refused at run time, not at build time. And in the editor, a node whose metadata is mid-edit is skipped with a warning rather than blanking your graph.