Common Errors

The messages an Indicator author actually meets, each with its cause and its fix, in the order the build produces them: declaration errors first, then the…

The messages an Indicator author actually meets, each with its cause and its fix, in the order the build produces them: declaration errors first, then the sheet validator, then the compiler, then the contract check, then the named runtime refusals, then the chart's own messages. Every heading is the text the tool prints, so a browser find on the message you got lands on its fix.

If your Indicator ran but drew nothing, the problem is probably not an error at all. Skip to Ran but blank at the end.

Where errors show

  • In the editor, Run stops at the first failing stage and shows each message as a squiggle under the offending span and as a row in the Problems lane, tagged with its stage (declarations, lint, metadata, compile, validate). Fix, Run again.
  • On your machine, om wrun build and om wrun validate print the same messages and exit non-zero. Declaration and sheet messages name src/indicator.ts:<line> or the sheet path (boxes.0.color); compiler messages carry the compiler's own file(line,column) pointer.

The build is staged, so one problem hides the ones behind it: a declaration error stops the build before the sheet is validated, and a sheet error stops it before the compiler runs. Declaration errors arrive together, one line each, under one header:

scaffold build blocked: code-first declaration errors
  src/indicator.ts:7: param options accept only { required, min, max, description }, not 'step'
  src/indicator.ts:12: option 'top' takes an output handle, not a string literal (bind a handle with a top-level const h = output(...) and pass h)
Declarations are extracted statically (the code never runs at build): string-literal names, literal defaults/options, source.field references, top-level statements in src/indicator.ts only.

option 'top' takes an output handle, not a string literal

Symptom: the build stops at the declarations stage with option 'top' takes an output handle, not a string literal (bind a handle with a top-level const h = output(...) and pass h), pointing at a box(...) or segment(...) line.

Cause: a box or segment coordinate is the value output(...) returned, not the output's name. box("band", { top: "hi", ... }) passes a string where a handle goes.

Fix: bind the output to a top-level const and pass the const. The same rule covers bottom, yFrom, yTo, when, and an output-valued from / to:

import { box, input, line, none, ohlcv, output, overlay, param } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_hi, out_lo, out_sma } from "./gen/outputs";
import { p_period } from "./gen/params";
import { Sma } from "./sdk/ta";

param("period", 20, { min: 1, max: 200 });
input("close", ohlcv.close);
output("sma", line, overlay);
// Bind each coordinate output to a const: the box names the handle, never the string.
const hi = output("hi", none);
const lo = output("lo", none);
box("band", { top: hi, bottom: lo, color: "#38bdf8", opacity: 0.15, borderWidth: 0 });

let sma = new Sma(20);
let value: f64 = NaN;

export function init(): void {
  sma = new Sma(i32(p_period()));
}

export function state(): i32 {
  value = sma.update(in_close());
  return isNaN(value) ? 0 : 1;
}

export function finalize(): void {
  out_sma(value);
  out_hi(value * 1.01);
  out_lo(value * 0.99);
  emitRow();
}

export function reset(): void {
  sma.reset();
  value = NaN;
}

segment 'ray' option 'yTo' references 'top', which is not an output handle

Symptom: segment 'ray' option 'yTo' references 'top', which is not an output handle; bind the output first (const top = output("...", ...)) and pass that const.

Cause: the identifier you passed is a variable, but not one bound to an output(...) call.

Fix: bind it with const, let, or var at the top level (the binding may sit below the shape that uses it). The sheet records the output's name, never the handle.

output(...) declarations must be top-level statements

Symptom: output(...) declarations must be top-level statements, not inside a function, class, or expression (or the same for param, input, box, segment, render.text, and the rest).

Cause: declarations are read from the text without running it, so one inside init(), inside an if, or inside a class is invisible to the sheet and refused.

Fix: move the declaration to the top level of the file. Declarations are static: they cannot depend on a param or a condition.

param options accept only { required, min, max, description }, not 'step'

Symptom: the family's option list, followed by the option you wrote.

Cause: every family lists its options in the message. step has no counterpart (settings take any value inside min..max); a kScript (legacy) label is description; kScript's constraints object does not exist.

Fix: drop or rename the option. The full option list per family is in Script definition.

duplicate output name 'sma'

Symptom: duplicate output name 'sma', duplicate param name, duplicate input name, duplicate box name, or duplicate render declaration name.

Cause: names are unique per family, and shapes share one namespace with outputs (below).

Fix: rename one of them.

expected a string literal

Symptom: expected a string literal, expected a numeric literal, option 'from' takes an integer bar offset literal or an output handle, or option 'panel' takes "overlay" or "lower" (a string literal), not 'price'.

Cause: a name built from a variable, a default computed from an expression, a fractional bar offset, or a panel outside the two names. The extractor reads literals only, because the code never runs at build time.

Fix: use literals: param("period", 20, ...), from: -4, panel: "lower".

code-first declarations must live in src/indicator.ts

Symptom: scaffold build blocked: src/helpers.ts:3: code-first declarations must live in src/indicator.ts (the build derives wrun/metadata.json from the entry file only).

Cause: machine workspaces only: a param, input, or output call in a second source file.

Fix: keep every declaration in src/indicator.ts. Helper files may hold functions and classes, never declarations.

code-first declarations need at least one input(...) and one output(...) statement

Symptom: code-first declarations need at least one input(...) and one output(...) statement (found 0 input(s), 1 output(s)).

Cause: a file with no input has no bar grid to walk, and a file with no output has nothing to compute.

Fix: declare the primary input (input("close", ohlcv.close) is the usual one) and at least one output, even a none.

input 'BTC-Close' and 'btc_close' both escape to accessor 'in_btc_close'

Symptom: wrun/metadata.json: input 'BTC-Close' and 'btc_close' both escape to accessor 'in_btc_close'; rename one so generated accessors stay unambiguous.

Cause: an accessor name is the declared name lowercased with every run of characters outside [a-z0-9_] collapsed to _. Two names that escape to the same identifier would silently share one accessor.

Fix: rename one of the two so the escaped forms differ. Prefer snake_case names in the file and the accessors read as written.

raw positional slot literals silently rebind

Symptom: scaffold build blocked: raw positional slot literals silently rebind when metadata params/inputs/outputs change, followed by one line per finding: src/indicator.ts:16: getFloat(0) -> use in_close() from ./gen/inputs in state() or p_period() from ./gen/params in init().

Cause: getFloat(0), setOutput(0, ...), and the raw host imports bind by position and rebind when a declaration is added above them. The build refuses them in every file under src/ except src/sdk and src/gen.

Fix: use the accessor the message names. src/sdk/sdk.ts stays available for variable-index access only.

params.0.default: default above max

Symptom: params.0.default: default above max, params.0.default: default below min, or params.0.min: min must be <= max, at the metadata stage.

Cause: the derived sheet is validated with the same schema the registry and the runtime use; a default outside its own range fails there.

Fix: move the default inside the range, or widen the range.

an output cannot color itself with color_by

Symptom: outputs.0.color_by: an output cannot color itself with color_by, outputs.0.color_by: color_by 'regime' does not match a declared output, or outputs.0.color_by: color_by needs 'colors' with at least 2 entries.

Cause: color_by names a different, declared output (usually a data-only none), and colors lists at least two entries.

Fix: declare the decision output and name it; give the palette two or more entries. width_by and widths follow the same rules (width_by needs 'widths' beside it (both or neither)), and shape_where cannot gate its own output (shape_where 'gate' does not match a declared output when the gate is missing).

color must be a hex, rgb() or hsl() color (the fill takes the opacity)

Symptom: boxes.0.color: color must be a hex, rgb() or hsl() color (the fill takes the opacity).

Cause: a named color ("red") cannot carry an alpha channel, and the box fill applies the opacity to its color.

Fix: use "#ef4444", "rgb(239, 68, 68)", or "hsl(0, 84%, 60%)". Segment and output colors are free-form strings; only the box fill has this rule.

a literal bar offset must be within -500..500 bars of the current bar

Symptom: segments.0.x_from: a literal bar offset must be within -500..500 bars of the current bar or segments.0.x_from: a literal bar offset must be an integer count of bars.

Cause: offsets are integers within 500 bars of the current bar.

Fix: stay within the range, or pass an output handle for a longer or data-driven reach: its per-bar value is truncated to the offset, and any offset clamps to the loaded range.

boxes must declare at most 16 entries

Symptom: boxes: boxes must declare at most 16 entries or segments: segments must declare at most 16 entries.

Cause: sixteen of each per sheet.

Fix: a repeating pattern is one declaration gated per bar, not one declaration per occurrence. The zone tracker draws every zone a side ever has with one box and a when gate (Limits).

box name 'range' is already taken by an output

Symptom: boxes.0.name: box name 'range' is already taken by an output; box and segment names must be unique across outputs, boxes, segments, renderers, and drawings.

Cause: one namespace for everything drawn.

Fix: rename the shape.

the primary input (index 0) cannot declare missing: "carry"

Symptom: inputSources.close.missing: the primary input (index 0) cannot declare missing: "carry": its rows define the request grid, so there is no earlier bar to carry a missing one from; declare "nan" or "zero" to densify the grid instead.

Cause: the first input(...) is the grid.

Fix: give it no missing policy (the usual case), or "zero" / "nan" to densify a sparse primary; put "carry", "zero", or "nan" on the secondary inputs (Data sources).

source 'trades' requires a side (BUY or SELL)

Symptom: inputSources.buy.side: source 'trades' requires a side (BUY or SELL) or inputSources.iv: source 'implied_volatility' requires a tenor (ONE_D, THREE_D, ONE_W, ONE_M, TWO_M, THREE_M, SIX_M, ONE_Y).

Cause: the per-source knobs: side on trades, tenor on implied_volatility and skew, token on token_supply.

Fix: add the knob to the input's options: input("buy", trades.volume, { side: "BUY" }).

input 'profile' declares cellType "array", but the cell channel needs abi_version "wrun-2"

Symptom: input 'profile' declares cellType "array", but the cell channel needs abi_version "wrun-2" (wrun-1 is frozen and has no cell imports), input 'profile' declares cellType "array" without max_cells; guests preallocate max_cells * 8 bytes and the host refuses bigger blocks, so the cap is required, or string_slots needs abi_version "wrun-2" (wrun-1 is frozen and has no string slots vocabulary).

Cause: hand-written sheets only: a celled input, a string slot, a renderer, or a drawing under the first runtime contract, or a celled input without its cap.

Fix: set abi_version: "wrun-2" in the sheet and give every celled input a max_cells. A code-first file never hits this: declaring any of those constructs derives the second contract automatically (Script definition).

max_bytes must be <= 4096 (the per-slot byte cap)

Symptom: string_slots.0.max_bytes: max_bytes must be <= 4096 (the per-slot byte cap), renderers.0.size: size must be >= 6, renderers.0.cells: table 'stats' declares 2x2 = 4 cells but lists 3, renderers.0.text: 'note' does not name a declared string slot, or renderers.0.y: 'mid' does not name a declared numeric output.

Cause: text fields name string slots, numeric fields name outputs, and the two never substitute for each other; text sizes are 6..64 pixels; a table lists exactly rows * cols cells; a slot holds at most 4096 bytes.

Fix: declare the slot or output the renderer names, and keep the numbers inside the caps (Drawing objects).

ERROR AS200: Conversion from type 'f64' to 'i32' requires an explicit cast.

Symptom: the compile stage fails with the compiler's own pointer:

ERROR AS200: Conversion from type 'f64' to 'i32' requires an explicit cast.
    :
 15 │ export function init(): void { sma = new Sma(p_period()); }
    │                                              ~~~~~~~~~~
    └─ in src/indicator.ts(15,46)

Cause: the file is AssemblyScript, TypeScript syntax over fixed-width numbers. Params and inputs are f64; a class period, a loop bound, an array index, or a counter declared as let count = 0 (an integer) is i32, and the compiler refuses to guess.

Fix: write i32(p_period()), put i32(...) around any float used as an integer, and declare float variables as f64 (let value: f64 = 0.0). Going the other way, an integer written to an output widens with f64(count):

import { input, line, lower, ohlcv, output, param } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_bars_above } from "./gen/outputs";
import { p_period } from "./gen/params";
import { Sma } from "./sdk/ta";

param("period", 20, { min: 1, max: 200 });
input("close", ohlcv.close);
output("bars_above", line, lower, { description: "Consecutive bars closing above the average" });

let sma = new Sma(20);
let streak: i32 = 0; // an integer counter: declare the type, never let it default from a float
let above: bool = false; // a truth value: compare to get one, never assign a number

export function init(): void {
  // Params are f64; a period is i32, so the cast is explicit.
  sma = new Sma(i32(p_period()));
}

export function state(): i32 {
  const close = in_close();
  const average = sma.update(close);
  if (isNaN(average)) return 0;
  above = close > average;
  streak = above ? streak + 1 : 0;
  return 1;
}

export function finalize(): void {
  // Outputs are f64; an integer goes out through an explicit widening.
  out_bars_above(f64(streak));
  emitRow();
}

export function reset(): void {
  sma.reset();
  streak = 0;
  above = false;
}

ERROR AS200: Conversion from type 'f64' to 'bool' requires an explicit cast.

Symptom: the compiler points at an assignment of a number to a bool.

Cause: ready = value where ready is a bool and value an f64. A number is not a truth value here.

Fix: compare: ready = !isNaN(value), above = close > average. (if (value) on a number does compile, as a nonzero test; write the comparison you mean anyway.)

ERROR TS2305: Module 'src/gen/outputs' has no exported member 'out_sma'.

Symptom: ERROR TS2305: Module 'src/gen/outputs' has no exported member 'out_sma'. followed by ERROR TS2304: Cannot find name 'out_sma'. at every call site.

Cause: you renamed or removed a declaration and the generated accessor went with it. The accessors are regenerated from the declarations before every build, so the compiler names every stale import and call.

Fix: update the import and the call: output("average", ...) is out_average(...) from ./gen/outputs, param("fast_len", ...) is p_fast_len(), input("btc_close", ...) is in_btc_close(). A Cannot find name on something that was never an accessor is a plain undeclared variable.

WRUN export 'finalize' has signature () -> f64; the wrun-1 contract requires finalize() -> void

Symptom: after a successful compile, the validate stage refuses the module:

WRUN export 'finalize' has signature () -> f64; the wrun-1 contract requires finalize() -> void. The wrun-1 contract is exactly: init(): void, state(): i32, finalize(): void, reset(): void: NO parameters and no return values except state's i32; params reach init and per-bar inputs reach state through the wrun_arg_* host imports (scaffold builds: the generated p_<param>() / in_<input>() accessors, in metadata order), outputs are written in finalize via wrun_output_f64(i, v) + wrun_result_write(count) (scaffold builds: out_<output>(v) + emitRow()), and all state lives in module-level variables.

Cause: a redesigned signature (init(args: Array<f64>), a finalize that returns the value) compiles under AssemblyScript and fails the static contract check afterwards.

Fix: keep the exact four signatures. Values leave through out_<name>(value) and emitRow(), never as return values; params arrive through p_<name>(), never as arguments.

wrun_cell_block_too_large

Symptom: an evaluation on your machine (a watch, om metric get, om metric series) refuses by name: the celled input's bar has 5 cells; max_cells is 4, so the evaluation is refused (a block is never truncated).

Cause: max_cells is a contract, not a hint: the module preallocates that many tuples, and a bar whose block is larger would overflow it.

Fix: raise max_cells on the input (input("profile", volume_profile.cells, { max_cells: 512 })) and rebuild. Nothing is ever truncated on your behalf.

wrun_cells_unavailable

Symptom: at plan time, before anything is fetched: input 'profile' declares cellType "array" but reads source '...', which serves one scalar per bar, not cell blocks; celled inputs read a celled source class (volume_profile, book), or the same code for the trade_volume_by_size class.

Cause: the input reads a class this data plane does not serve, or a scalar source under a celled declaration.

Fix: read volume_profile or book for celled data; side-split trades.volume for the tape's scalar halves (Data sources).

wrun_celled_metric_unsupported

Symptom: om metric screen or om backtest refuses a package that has a volume_profile or book input, naming the input (and the composition path in when a scalar package reaches it through a metric source).

Cause: the screen and backtest replay paths carry no cell blocks yet.

Fix: evaluate celled packages through watches, om metric get, om metric series, and chart previews; screen and backtest a scalar package.

wrun_source_set_generated

Symptom: om wrun source set refuses with wrun/metadata.json: this workspace is code-first: the sheet is generated from src/indicator.ts declarations and every build regenerates it from source, so direct sheet edits are rejected; the agent's metadata argument refuses the same way as wrun_metadata_generated.

Cause: a derived sheet is derived state. A direct edit would be clobbered by the next build.

Fix: edit the declaration instead (input("yes_odds", odds.close, { symbol: "0x...", outcome: "YES" })) and rebuild. To hand-edit the sheet again, remove every declaration from the source, or delete generated_from and source_digest from the sheet.

wrun_preview_displacement_unsupported

Symptom: om chart indicator preview refuses an output whose displacement_bars is nonzero: declared, not yet honored by chart preview.

Cause: the preview wire carries times and values and no displacement field, so a displaced output would render on the bar it was computed on.

Fix: preview an undisplaced output. Alerts, om metric get, and om metric series read the undisplaced value by design; the chart host applies the shift.

wrun_render_result_too_large

Symptom: a run refuses because the expanded render selection (every selected text mark, label, table cell, shape, and drawing) passed 2 MiB.

Cause: a per-bar text renderer over a long history, or a large table rewritten on every bar.

Fix: write the slot only on the bars that need a mark (an unwritten slot draws nothing), or move the readout to render.label, which keeps one label. The related string caps refuse the same way, never truncating: 4096 bytes per slot, 64 KiB per row, 8 MiB per run.

Messages from the chart

  • Indicator compiler unavailable: ...: the browser could not load the compiler (an offline tab, a blocked worker). Reload; nothing in the file is wrong.
  • The package downloads once the indicator compiles cleanly and Publish is available once the Indicator compiles cleanly: fix the Problems lane first; both buttons unlock on a clean Run.
  • The publish window was blocked. Allow popups for this site and try again. and The registry did not answer in time. Try again.: the publish popup, not your file.
  • Publish the Indicator before adding an alert.: alerts watch published versions only. Publish, then add the alert on the published row.
  • Indicator alerts are not enabled yet.: alerts on Indicators from the chart are behind a flag on this site today. Install the package on your machine and put a watch on the metric instead (Alerts).
  • This Indicator reads another market. Alerts evaluate the chart's own market only. and This Indicator is pinned to a different interval than the chart.: a hosted alert refuses symbol, exchange, and interval pins. Alerts on pinned packages run on your machine.
  • This Indicator reads a data source alerts cannot evaluate yet.: the hosted engine does not fetch that source class. Your machine does.
  • trades volume needs side 'BUY' or 'SELL', source class 'odds' is not served by the browser lane yet, funding field 'rate_open' is not served by the browser lane, implied_volatility needs tenor 'ONE_W', 'ONE_M', or 'THREE_M': the chart serves a subset of the sources catalog (Execution model lists it). The package is fine on your machine.
  • Volume profile data is unavailable for '...' (the volume_profile source lane answered empty or was declined), so the indicator cannot compute.: the chart's profile lane returned nothing for this market; switch to a market the venue serves profiles for.

Ran but blank

An Indicator that builds cleanly and draws nothing has no error to show, only a symptom. The kScript (legacy) engine reports these as runtime diagnostics; an Indicator makes the same states visible through its rows:

SymptomWhat is happeningFix
nothing on any barstate() returns 0 on every bar: a class whose period exceeds the loaded history, or a guard that never passesload more history; probe the guard as a none output (Debugging)
a line is empty on every barthe output is written NaN on every bar, or it is declared nonefind the first NaN input; change the plot kind
a line stops partwaya value that went NaN mid-history: a division by zero, a missing: "nan" secondary with no more observationsguard the division; choose "zero" or "carry"
very few marksa shape gate that rarely firesusually intentional; lower the threshold to check the gate works
empty on the chart, fine on your machinea symbol, exchange, or interval pin the chart ignores, or a source the chart's market does not serveread the chart's message; evaluate on your machine
fine on closed bars, wrong on the newesta field reset() forgotreassign every module-level variable in reset()

The workflow that turns a symptom into the line that causes it is in Debugging.