21. Errors
Format: filename:line:col: error: <message> (LSP-compatible), compile-time, with a span.
--strict promotes warnings to errors; --no-warn silences them (SPEC 20).
Every diagnostic carries a stable code — a phase letter (Lex · Parse · Resolve ·
Validate · laYout · rouTe · Output) then a 3-digit number, e.g. V001. Codes are stable once
assigned; the message may still improve. The implementation’s diagnostic
registry is the authority for code assignment — this section’s tables and
their ordering carry no codes. The human form above stays code-free; lini --json
(SPEC 20) emits the structured record — code, severity, span, related span, and a
machine-applicable replacement where one exists.
What the --json document freezes. { "file", "diagnostics": [ … ] }, each
entry code · family · severity (error / warning) · message ·
span, then related and suggestion (span · replacement ·
applicability) where the diagnostic has them; every span carries
start · end (byte offsets) and 1-based line · col · endLine ·
endCol. A tool may rely on that shape and those codes; a message
may be reworded to read better, and a later release may add fields — never
rename or drop one. A clean file emits an empty diagnostics array, not an
error.
Lexing
| Condition | Message |
|---|---|
| Unclosed string | unterminated string literal |
| Bad number | invalid number literal |
| Bad escape | invalid escape sequence '\X' |
| Stray character | unexpected character 'X' |
| Operator outside a group | math operators appear inside ( ) — e.g. padding: (8 * 2) |
Properties & validation (SPEC 17’s strict/lenient rule)
| Condition | Message |
|---|---|
| Unknown property name | unknown property 'colr'; did you mean 'color'? |
| Misused property, wearer known | 'points' has no meaning on '|box|' — it reads on '|line|' / '|poly|' · 'cell' places a grid or schematic child — this box sits in a 'layout: flow' |
| Property dead for every wearer | '.hot { cell: … }' is inert on every wearer (warning) |
| Class defined, never worn | class '.hot' is never worn (warning) |
| Malformed value | 'opacity' is a fraction 0..1 · 'translate' takes 'x y' · 'padding' takes one value, not a comma list · 'wavy' waves a link's wire — a shape's outline takes solid, dashed, dotted, center, or phantom |
| A space-separated list where the comma law reads items | 'data' takes comma-separated values — 'data: 9, 15, 24' (SPEC 2) |
| Deferred property | 'legend' is named but not built yet — see SPEC 24 — a named-but-unbuilt row (SPEC 24) errors, so accepting it can never freeze the non-behaviour |
| Property on a link that has no link meaning | 'routing' is a scope's strategy — one scope, one strategy; set it on the container (clearance: is a link’s, ROUTING.md) |
radius on a non-rect primitive | 'radius' rounds a rect or a polyline join — rounding a '|hex|' is deferred (SPEC 24) |
Arbitrary numeric font-weight | 'font-weight' takes normal, medium, semibold, bold, or 400, 500, 600, 700 (100–900 is deferred) |
| Gradient in a text-colour slot | 'color' takes a flat colour — a gradient fills a shape, and gradient-on-text is deferred |
% outside a colour component | 'width' takes a number — a '%' is a colour component (SPEC 2) |
Identity, cascade & statements
| Condition | Message |
|---|---|
| Duplicate id | duplicate id 'X' (previously at L:C) |
| Unknown type / class | unknown type 'X' / unknown class '.X' |
| Inheritance cycle / depth | cycle in 'X → … → X' / 'X' exceeds max inheritance depth (16) |
| Define shadows builtin | 'X' shadows a built-in type |
| Empty bars | '| |' needs a type or an '#id' |
| Invalid id | '#123' is not a valid id — an id starts with a letter or '_' |
| Reserved id prefix | an id may not begin 'lini-' — the prefix is reserved for generated names |
| Class inside the bars | a class follows the bars — write '|box| .hot', not '|box.hot|' |
| Symbol set twice | an icon's symbol is its label or 'symbol:', not both |
| Text carries children | text content takes no '[ ]' — wrap it in '|block|' to give it children |
| Box property on text | 'pin' needs a box — wrap the text in '|block|' |
| Declaration outside a block | a declaration belongs in a '{ }' block |
| Bare node on the canvas | a node leads with bars — write '|box#X|' (a bare name is a link endpoint) |
| Bare type in the stylesheet | a type only appears in bars — write '|box| { }' to style every box |
| Missing declaration ‘;’ | a declaration ends with ';' |
| Style block holds non-decl | a '{ }' style block holds only declarations |
[ ] holds a declaration | declarations go in '{ }', not '[ ]' |
| Two head labels | one inline label — put two or more in a '[ ]' |
| Label after a class | a label comes before classes — write '|box| "X" .hot' |
| Stylesheet after canvas | the stylesheet '{ }' must come first, before any instance |
| Glued compound in a rule | a selector unit can't glue a type and a class — space them (descendant) or style '.hot' |
| Spaced class chain | classes glue into a chain — write '.hot.loud', no space |
Links & routing
| Condition | Message |
|---|---|
| Unknown endpoint (path) | link endpoint 'X' not found at <scope> + ; did you mean 'A', 'B'? |
| Auto-create shadows a node | endpoint 'X' auto-created at <scope> — a node 'X' also exists at 'A.B.X' (warning) |
| Auto-create near-miss | 'cta' auto-creates a new box; did you mean 'cat'? (warning — edit distance ≤ 2 or case-fold vs names known in scope, SPEC 3) |
| Chain mixes operators | link chain mixes operators 'X' and 'Y' |
| Chain < 2 nodes | link requires at least two endpoints |
Bare o marker | '-o' needs a max glyph — write '-o<' or '-o+'; the hollow ring is an ER component only |
| Missing required property | '|line|' requires 'points' |
-> in the stylesheet | '->' draws a link on the canvas — style every link with '|-| { stroke: … }' in a '{ }' block |
|-| / |link| as an instance | a link is drawn by an operator — '|-|' only styles links (write 'a -> b') / links are drawn by operators, not the '|link|' type |
|node| as instance | 'node' is the umbrella concept — write '|block|' for the bare box |
| Unknown routing strategy | routing takes orthogonal, natural, or straight |
| Unknown side | ':X' is not a side — use top, bottom, left, or right |
| Link labels split | keep a link's labels together — write 'a -> b [ "x" "y" ]' (warning) |
| Capsule endpoint in a drawing | a drawing never invents an endpoint — declare the node, then annotate it |
| Pin path on an inline component | '|component#U9|.p4' — an inline component has no authored pins |
Values, colour & expressions
| Condition | Message |
|---|---|
| Invalid / out-of-range color | invalid color 'XYZ' / rgb(300,0,0): component out of range |
Invalid oklch() | oklch expects (L, C, H) or (L, C, H, A) — L and A in 0..1, C ≥ 0, H in degrees |
| Gradient with < 2 stops | gradient() needs at least two colour stops |
linear-gradient without an angle | linear-gradient needs an angle first, then ≥ 2 colour stops — e.g. linear-gradient(135, --teal, --sky) |
| Single-quoted string | single quotes are not strings — use "…" |
| Unquoted text value | 'title' takes a quoted string — write title: "…" |
Invalid pin value | 'pin' expects none, center, an edge (top/bottom/left/right), or a corner (e.g. 'top right') |
Negative container gap | a container's 'gap' must be ≥ 0 — a mate’s gap: may go negative (SPEC 15.5) |
skew out of range | skew: N must be in (-89, 89) |
| Unknown name in an expression | unknown name 'foo' in an expression |
| Function arity | 'sin' takes 1 argument(s), got 2 |
| Spaced call paren | a call's '(' glues to its name — write 'rgb(…)' |
hatch() off fill | 'hatch' is a fill — 'stroke' takes a colour or gradient |
| Unreadable image path | cannot read image './logo.svg' — no such file (SPEC 7) |
| Asset escapes the served root | '../secret.svg' resolves outside the served root (SPEC 20) |
Layout — grid
| Condition | Message |
|---|---|
Missing columns | 'layout: grid' requires 'columns' |
| Empty / bad track | 'columns' needs at least one track / a track is a size, 'auto', or repeat(N[, size]) |
| Grid out of range | cell: 5 _ exceeds columns=3 |
Layout — wrap (SPEC 5)
| Condition | Message |
|---|---|
nowrap text can’t fit | text cannot fit 'max-width: 80' without wrapping — widen it or drop 'text-wrap: nowrap' |
| Non-text child wider than the cap | a child is wider than 'max-width: 80' — only text wraps |
width above max-width | 'width: 200' exceeds 'max-width: 120' |
Layout — tree (SPEC 12)
| Condition | Message |
|---|---|
|topic| outside a tree | '|topic|' builds a tree — it belongs in a 'layout: tree' |
| No root topic | a tree needs exactly one root '|topic|' |
| A second root topic | a tree has one root — '|topic|' 'X' is a second |
side: top/bottom in bilateral | a bilateral tree grows left and right — 'side' takes left or right |
side: in row / column | 'side' picks a bilateral branch's half — this tree has one growth direction |
Unknown direction: | unknown direction 'radial' — a tree grows column, row, or bilateral (a ring-radial tree is deferred, SPEC 24) |
Layout — sequence
| Condition | Message |
|---|---|
| Sequence node outside a sequence | '|loop|' belongs in a 'layout: sequence' (same for |opt| / |alt|; a |note| is core — SPEC 8) |
|else| outside an |alt| | '|else|' separates an '|alt|' — write it inside one |
|note| in a sequence, no placement | a sequence '|note|' needs 'place:' |
Bad place: | 'place' is a mode then its lifelines — 'place: over api db', 'place: left api' |
| Sequence property off a sequence | 'place' is valid only in a 'layout: sequence' (same for activation) |
Layout — chart & pie
| Condition | Message |
|---|---|
| Series / axis / band / mark outside a chart | '|bars|' is a chart series — it belongs in a 'layout: chart' · '|axis|' belongs in a 'layout: chart' |
|slice| outside a pie | '|slice|' belongs in a 'layout: pie' |
| Pie given an axis or series | a pie's children are '|slice|' only |
A |slice| with children | a '|slice|' is one wedge — multi-ring pie / sunburst is deferred (SPEC 24) |
| Empty chart / pie | a chart needs at least one series / a pie needs at least one '|slice|' |
Series with both / neither data: fn: | a series takes 'data' or 'fn', not both / a series needs 'data' or 'fn' |
arrow / crow marker on a series | 'marker: arrow' has no centred form on a chart — use dot, circle, or diamond |
fn: list ≠ band count | 'fn' has N formulas but the chart has M bands |
| Data ≠ categories count | series data has N values but the chart has M categories |
labels: count ≠ data count / on fn: | 'labels' has N entries but the series has M data points / 'labels' needs explicit 'data' |
categories: + an axis text | set 'categories' or an axis 'labels', not both (⌛ — reachable once per-axis tick text lands, SPEC 24) |
|mark| without axis: / bad at: | a '|mark|' needs 'axis:' to place it / 'at' takes one value (a line) or two (a point) |
|bubble| missing at: / value: | a '|bubble|' needs 'at:' (x y) and 'value:' |
Unknown axis: id | axis 'X' not found + ; did you mean 'Y'? |
range: bad / equal ends | 'range' takes two ends: 'a b', 'a auto', or 'auto b' / 'range' needs distinct ends |
span: on a |band| | a band's extent is 'range: a b' — 'span' places a grid child (SPEC 14.5) |
scale: log over a non-positive domain | a 'scale: log' axis needs a domain above 0 |
| Paint list count ≠ data count | 'fill' lists N paints but the series has M data points |
Paint list on |line| / |area| | a '|line|' is one shape with one paint — per-datum lists read on '|bars|' / '|dots|' |
| Mixed date / numeric domain | an axis reads dates or numbers, never both — one domain, one kind |
| Invalid date literal | '2026-13-01' is not a date — ISO-8601: '2026-01-31', optionally 'T09:30' and 'Z' |
Numeric step: on a time axis | a time axis steps by calendar — 'step: month', 'step: 2 week' |
Bad format: value | 'format' takes auto, decimal N, significant N, scientific N, engineering N, percent N, fraction D, or a date preset |
side: in direction: radial | 'side' has no meaning in a radial chart — it has one radius axis |
|band| / |mark| in direction: radial | a radial chart draws no bands / marks yet — remove it or change 'direction' (SPEC 24) |
hole: out of range | 'hole' is a fraction 0..1 |
| Negative slice value / pie total zero | a '|slice|' value must be ≥ 0 / a pie's slice values sum to zero |
Layout — drawing (SPEC 15)
| Condition | Message |
|---|---|
|sketch| without draw: | '|sketch|' requires 'draw' |
|hole| / |pitch-circle| / |magnifier| without width: | '|hole|' requires 'width' — its diameter |
| Unknown pen call / arity | unknown draw call 'X' / 'arc' takes (dx, dy, r) or (r, deg) |
fillet / chamfer off a corner | 'fillet' modifies the corner between two segments |
Floating :segment | a ':segment' glues to its call — name a station with point():v |
Bare point() | 'point()' names the pen's position — attach a ':segment' |
| Arc radius too small | arc radius N is smaller than half the chord |
Bad mirror: item | 'mirror' takes x-axis, y-axis, a bearing, or none |
Axis mirror: on |path| / |image| | '|path|' has no reflection — draw it with the pen |
Bad break: group | 'break' takes two stations 'a b' — a < b — and an optional x-axis / y-axis |
break: off a sketch | 'break' cuts a '|sketch|' — draw the profile with the pen |
break: station off the profile | 'break' at N misses the profile |
Overlapping break: groups | 'break' spans overlap — merge them |
break: through a cubic | a 'break' can't cut a 'curve()' — move the stations (SPEC 24) |
| Drawing statement outside a drawing | '(-)' draws a dimension — it belongs in a 'layout: drawing' (or its 'floorplan' dialect) (same for (o), (<), ||, corner anchors, tol:, …) |
| Unknown endpoint | dimension endpoint 'X' not found at <scope> + suggestions — never auto-created |
| Corner order | ':right-top' is not an anchor — did you mean ':top-right'? |
(>) | '(>)' is reserved — the angle op is '(<)' |
One-ended (-) / || | a linear dimension measures two anchors / a mate seats two parts |
Two-ended (o) | '(o)' measures one round feature — write 'a:top (o)' for a span |
| Empty one-ended leader | a leader needs its text — 'bolt <- "THRU"' |
One-ended -> / -* | a leader points back at its feature — write 'a <- "…"' |
Bare (o) with no axis | '(o)' can't pick an axis on 'X' — anchor a side ('X:top (o)') or a segment |
(<) on a point anchor | an angle reads two edges — a named segment, a '|line|', or a side |
Unary (<) on an unmirrored name | '(<)' on ':taper' needs 'mirror:' — no twin to measure against |
Station ⌀ on a mirror-only profile | a station '⌀' reads a revolved profile — 'revolve: x-axis' |
revolve: + mirror: together | a sketch takes 'revolve:' or 'mirror:', not both |
Bad revolve: value | 'revolve' takes x-axis or y-axis |
Bad thread: group | 'thread' takes a segment and its pitch — 'thread: m8 1.5' |
thread: without revolve: | 'thread' dresses a revolved profile — add 'revolve: x-axis' |
thread: segment off-axis / not straight | 'thread' runs along the axis — 'm8' must be a straight run parallel to it |
Unknown thread: segment | no segment 'm8' in this 'draw:' + suggestions |
thread: on a non-round node | 'thread' dresses a '|sketch|' segment or a round feature |
Bad sheet: | 'sheet' takes a size — a5…a0 (ISO) or a…e (ANSI) — and an optional portrait / landscape + did-you-mean |
of: finds no marker | 'of' finds no marker 'X' |
of: names a non-marker | 'of' names 'X', not a '|plane|' or '|magnifier|' |
| Detail of a sourced view | a detail magnifies a base view — 'of' can't name a marker inside another sourced view |
at: off the model | a 'plane' at N sits off the model |
Bad facing: | 'facing' turns the arrows — left, right, up, or down |
| Marked projection op | a projection line is unmarked — write 'side.screw:head - end.od:top' |
| Projection ends in one view | a projection link ties two views — both ends read 'side' |
| Projection end off a view | a projection link ties drawing anchors — 'notes' is not in a drawing view |
| Cross-view dimension / mate | a dimension reads one view — a cross-view correspondence is a construction link ('a - b') |
| Authored cell on a generated field | cell 2 1 is taken by the generated 'Rev' field — place it after the fields |
:segment shadows a built-in point | ':left' is a built-in anchor — pick another name |
Unknown :segment | no segment ':step' on 'body' + suggestions |
Duplicate :segment in one draw: | ':step' is already named in this 'draw:' |
| Label on a mate | a mate takes no label |
gap: on a point mate | a point mate coincides — 'gap' needs directed anchors (sides or named edges) |
| Non-parallel mate directions | mated anchors must face along one axis — 'a:left || b:top' has no shared normal |
| Over-constrained mate | mate over-constrains 'X' — already positioned via 'A || B' |
| Mate within one part | 'a' and 'b' are features of one part — a part is rigid |
| Perpendicular directed pair | 'a:left (-) b:top' — perpendicular faces have no shared normal; the angle between edges is '(<)' |
project: vs a directed anchor | 'project: vertical' conflicts with 'a:left' — the directed anchor reads horizontal |
| Unknown copy index | no copy 'bolt.5' — the replication places 4 |
| Duplicate datum letter | datum 'A' is already placed (previously at L:C) |
| Drafting type outside a drawing | '|feature-control|' annotates a drawing — it belongs in a 'layout: drawing' (or its 'floorplan' dialect) (same for |surface-finish|, |control|, |datum|) |
| Unknown characteristic | unknown characteristic 'flatnes'; did you mean 'flatness'? |
| Characteristic set twice | a control's characteristic is its label or 'characteristic:', not both |
| Unknown finish variant | 'symbol' picks the vee — basic, machined, or prohibited |
Control row without tol: | a control row needs 'tol' — its zone width |
| Mixed frame forms | a frame is one row or '|control|' rows — not both |
|control| outside a frame | '|control|' is a '|feature-control|' row |
datums: on a form control | 'flatness' is a form control — it takes no datum |
| Missing required datum | 'circular-runout' measures against a datum — name one in 'datums:' |
| Unknown datum reference | no datum 'D' in this drawing — declared: A, B |
| Too many datums | 'datums' orders primary, secondary, tertiary — three at most |
zone: off an axial control | 'zone: diameter' has no meaning on 'flatness' — its zone is a width, not an axis |
material: off a feature-of-size control | 'material' modifies a feature-of-size control — position, orientation, or straightness |
| Unknown modifier | 'modifiers' takes projected N, free-state, or tangent-plane |
| Point-target seat | a seat needs a face — anchor a side or a named edge ('sf || plate:top') |
| Seat with no geometry end | a seat stands an annotation on geometry — 'sf || n1' seats nothing |
| Annotation seated twice | 'sf' is already seated (previously at L:C) |
| Annotation node on a routed link | a routed link's '[ ]' holds text labels — annotation nodes ride a drawing's dimensions and leaders |
& fan on a measuring op / mate | '&' fans one-ended leaders — chain dimensions instead ('a (-) b (-) c') |
gap: on a dimension | a dimension stands off by 'clearance' — 'gap' is a mate's separation |
side: off-axis | a horizontal dimension stacks on top or bottom / a vertical dimension stacks on left or right |
Parallel (<) edges | the angle's edges are parallel — they never meet |
Bad tol: | 'tol' takes a number, '+upper -lower', or a fit ident |
Bad pattern: | 'pattern' takes grid(cols, rows, dx, dy) or radial(count, radius) (name and arity) · 'radial' needs count ≥ 2 and radius > 0 |
scale: ≤ 0 | 'scale' must be > 0 |
scale: on a |page| | a '|page|' carries no 'scale:' — 'density:' sets its pixels per millimetre (root), a drawing's 'scale:' its drafting ratio |
density: ≤ 0 | 'density' must be > 0 |
density: under unit: px | 'density' is pixels per millimetre — 'unit: px' has none; drop it, or state 'unit: mm' to scale (warning — only the identity density: 1 applies, SPEC 15.1) |
| Absurd rendered extent | the drawing renders 48000 px wide — 'scale:' is a ratio; a 5 m beam at 1:50 is 'scale: 0.02' (hint) |
Bad unit: / density: off the root | 'unit' is px, mm, cm, m, or in / 'density' is scene config — set it in the root block |
| Chain past a label | a text callout ends its statement — chain before it |
| Mate in a flow scope | a '|row|' places its own children — mates seat a drawing's |
| Empty drawing | a drawing needs at least one geometry child |
Layout — floorplan (SPEC 15.11)
| Condition | Message |
|---|---|
| Floorplan type outside the scope | '|wall|' belongs in a 'layout: floorplan' (every floorplan type) |
on: an unknown / curved segment | 'sout' is not a segment of this wall; did you mean 'south'? · an opening sits on a straight run — ':bay' is an arc |
| An opening off its segment | 'd2' at 1.8 + width 0.9 overruns 'side' (length 2.3) |
| Overlapping openings | 'entry' and 'w1' overlap on 'south' |
An opening outside a wall’s [ ] | a '|door|' rides in its wall's '[ ]' |
translate: on an opening | an opening sits at 'on:' / 'at:' — move the station, or nudge the wall |
curve() in a wall’s draw: | a wall bends with 'arc()' — 'curve()' has no offset |
hinge: / swing: on a sliding door | a sliding door has no leaf to hang — remove 'hinge:' / 'swing:' |
A wall segment authored *-in / *-out | ':north-in' collides with the derived face anchor — rename the segment |
| An arc wall tighter than its thickness | arc radius 40 is under thickness/2 — the inner face vanishes |
Missing on: / steps: | required-property errors, as points on a |line| |
Layout — schematic (SPEC 16)
| Condition | Message |
|---|---|
| Schematic type outside the scope | '|R|' belongs in a 'layout: schematic' (every schematic type) |
:side on a terminal | a terminal owns its connection — a pin or label takes no ':side' |
| Non-90° rotation on a connection-bearing part | a schematic part rotates in 90° steps — 0, 90, 180, or 270 |
A mirror: on such a part naming no axis of its own | a schematic part mirrors about its own axis — x-axis, y-axis, or none |
| Pinless wire to a 3+-pin part | 'U7' has 21 pins — name one ('U7.VS') |
| Both pins of a 2-pin part taken | both pins of 'R5' are wired — name one ('R5.p1') |
| Minted ref as an endpoint | link endpoint 'R1' not found — a minted ref is display-only; give the part an id to wire it |
| Marker on a part-to-part wire | a schematic wire is plain — markers shape a text label's tag; write 'a - b' |
| Marker at a symbol-form label | '|gnd|' draws its symbol — there is no tag to shape |
| Bare unknown id in the scope | 'NSTDBY' is unknown — a schematic never invents a box; did you mean '- "NSTDBY"' (a net label)? |
| Duplicate wire | 'a - b' is already wired — a repeated wire means nothing on a sheet |
| Dot-path into a label | a label is its own terminal — it has no pins |
Unknown schematic symbol: | unknown symbol 'gnb'; did you mean 'gnd'? |
Bad shape: / pins: / number: | 'shape' takes plain, left, right, both, or round — not 'X' · 'pins' takes a count ≥ 1 · 'number' takes an integer |
clearance past the fine pitch | 'clearance' 30 is past the sheet's pin pitch 20 — two wired pins stand one pitch apart, so no wire could keep it |
Non-integer columns | 'columns' in a schematic is the wrap count — a positive integer (its tracks size to their anchors) |
Two anchors on one cell: | cell 2 1 already holds 'U7' — give 'U8' its own ordinal |
| Satellite chain with no placed end | 'C7' has no placed end — its chain falls back to the flow (warning) |
| A chain’s third placed end | 'U3.a' also holds 'C7' — a chain distributes between two placed ends, so this one is dropped (warning) |
Routing — a stray’s reasons (ROUTING.md, Impossible layouts / Fixed ports); each is a warning naming the link, --strict an error
| Condition | Message |
|---|---|
| Closed graph | no legal route: every side entry or channel is closed at this layout |
| Blocked fixed port | fixed port blocked: a body covers the port's landing |
| Crowded fixed ports | fixed ports closer than the minimum pitch on one side |
| Conflicted fan | fan ends carry two different fixed ports |
| Pinned self-loop | self-loop with both ends forced onto one side |
Output (SPEC 18)
| Condition | Message |
|---|---|
--static cannot outline a run | no bundled glyph for '你', '好' in "你好" — under --static this text stays <text>, drawn by the viewer's fonts (warning) |