Skip to content

Structured Errors

Skein compiler errors are structured data, not just human-readable strings. Every error can serialize to JSON and includes fields designed for LLM consumption.

This is a core feature of the language — when an AI agent generates Skein code that fails to compile, the structured error gives it enough information to understand and fix the issue automatically.

%Skein.Error{
code: "E0001", # Stable error code
severity: :error, # :error or :warning
message: "Unexpected token '}'", # Human-readable description
location: %{ # Where in the source
file: "hello.skein",
line: 5,
col: 12
},
context: nil, # Optional surrounding code context
fix_hint: "Expected expression after 'let x ='", # What to do about it
fix_code: nil, # Optional suggested code fix
span: nil, # Exact extent of the problem (1-based, end-exclusive)
edit_kind: nil # How to apply fix_code mechanically (nil = template)
}
FieldTypePurpose
codeStringStable identifier for the error category (e.g., "E0001")
severityAtom:error (compilation fails) or :warning (compilation proceeds)
messageStringHuman-readable description of the problem
locationMapSource file, line number, column number
contextString or nilThe code around the error for display
fix_hintString or nilSuggestion for how to fix the issue
fix_codeString or nilExact code that would fix the issue
spanMap or nil%{start: %{line, col}, end: %{line, col}} — the exact source extent (1-based; end.col exclusive)
edit_kindAtom or nilHow to apply fix_code as a mechanical edit (see below)

When fix_code is an exact edit (not an illustrative template), the error also carries span and edit_kind, so any consumer — the LSP, skein mcp clients, agents — can apply the fix without per-error-code logic:

edit_kindApplication
:replaceReplace the spanned text with fix_code (empty fix_code deletes it)
:insert_beforeInsert fix_code immediately before span.start
:insert_afterInsert fix_code immediately after span.end
:insert_lineInsert fix_code as a new line at span.start.line, indented to span.start.col
:delete_lineDelete the line(s) from span.start.line through span.end.line

Skein.Error.Edit.apply_fix/2 is the reference implementation of these semantics. A nil edit_kind means the fix_code (if any) is a template like "fn name() -> Type { ... }" — useful as a hint, not a verbatim edit.

Errors serialize to JSON for tool integration:

{
"code": "W0001",
"severity": "warning",
"message": "Unused binding 'order'",
"location": {
"file": "hello.skein",
"line": 5,
"col": 3
},
"fix_hint": "Remove this binding or prefix with _ to indicate it is intentionally unused",
"fix_code": "_order",
"span": { "start": { "line": 5, "col": 7 }, "end": { "line": 5, "col": 12 } },
"edit_kind": "replace"
}

Errors are returned as lists throughout the pipeline:

# Lexer error
{:error, [%Skein.Error{code: "E0001", message: "Unexpected character: #"}]}
# Parser error
{:error, [%Skein.Error{code: "E0001", message: "Expected 'module', got 'ident'"}]}
# Code generator error
{:error, [%Skein.Error{message: "Core Erlang compilation failed: ..."}]}

The compiler’s with chain short-circuits on the first error list.

All error codes are aligned with the language specification. Agents can rely on stable codes for automated error handling.

CodeSeverityDescription
E0001errorUnexpected token
E0002errorInvalid string: unterminated literal, expression inside ${...} interpolation, empty interpolation (${}), or unterminated interpolation
E0003errorInvalid number literal (e.g. underscore grouping in a float: 1_000.5)
CodeSeverityDescription
E0010errorUndefined identifier, unknown &fn reference, call to an undeclared fn, or unknown store-table method
E0011errorDuplicate definition
E0016errorCross-module function call (functions are module-private; expose a tool instead)
CodeSeverityDescription
E0012errorMissing capability for effect call
E0013reservedCapability parameter mismatch
E0014errorTool name not declared
E0015errorDuplicate tool short name
E0017errorDuplicate scoped capability declaration (memory.kv, event.log, process.spawn, timer allow one per module or agent)
E0028errorScenario capability envelope missing/incomplete: a tool the scenario calls has no capability tool.use(T) envelope, or the envelope does not cover the tool’s transitive effect summary
E0029errorEffect in a pure context: a test body or a scenario implement provider block reaches a capability-gated effect, directly or transitively through local fn calls/&fn references — effects belong in scenario, and providers must be pure

Example:

{
"code": "E0012",
"severity": "error",
"message": "Capability 'http.out' required but not declared. Effect calls to 'http' require this capability.",
"location": {"file": "service.skein", "line": 5, "col": 5},
"fix_hint": "Add a capability declaration to the module: capability http.out",
"fix_code": "capability http.out"
}

The fix_code field is especially useful for LLM agents — it provides the exact text to insert to resolve the error.

CodeSeverityDescription
E0020errorType mismatch (including wrong argument counts or types for fn, stdlib, effect, and fn-typed-variable calls, wrong-shape callbacks in higher-order slots, interpolation in string patterns, non-scalar interpolation segments, tool implement bodies vs the Result[output, error] contract, provider bodies vs their declared return, a bare fn name used as a value, and calling a non-function value)
E0021errorNon-exhaustive match
E0022errorInvalid ! on non-Result
E0023errorInvalid ? on non-Result, enclosing fn doesn’t return Result, or the propagated error type is incompatible with the enclosing Result’s error type
E0024errorUnknown type name; or non-exhaustive match on an enum/Result/Option missing variant patterns
E0025errorConstraint annotation on wrong type
E0026errorInvalid named argument (unknown/duplicate name, positional after named, callee without named-argument support)
E0027errorInvalid guard expression (guards allow literals, bindings, field access, comparisons, boolean operators, and +/-/* arithmetic)
E0037errorUnverified type at a declared boundary: a value whose type is unknown, or whose branches produced incompatible types, cannot cross a declared fn return
E0038errorProvider contract violation: a scenario implement block whose signature does not match its capability’s provider contract, or an implement under a capability with no provider contract
CodeSeverityDescription
E0030errorInvalid phase transition
E0031warningUnreachable phase
E0032errorPhase handler missing
E0033errortransition() outside an agent, or in an agent that declares no Phase enum
E0034errorsuspend() outside agent handlers
E0035erroridempotent() outside handler bodies
E0036errorstop() outside agent handlers
E0039erroremit outside agent handlers (module code records events with event.log)
CodeSeverityDescription
E0040errorInvalid supervisor strategy
E0041errorInvalid max_restarts value
E0042warningSupervisor has no children
E0043errorInvalid store.table declaration (typed tables: the capability names a declared record type with exactly one @primary field)
CodeSeverityDescription
W0001warningUnused binding
W0002warningUnused capability
W0003warningUnreachable code after stop()
W0004warningEnum match covers only specific values of a variant (add a binding arm or wildcard)

E0013 (capability parameter mismatch) is reserved: the code is allocated and documented, but no compiler path constructs it yet. It keeps its meaning when first emitted — error codes are append-only.