Errors¶
This page lists every diagnostic code Rad can emit, with examples and fixes.
You can also read any entry from the terminal with rad docs <code>,
e.g. rad docs RAD10001.
Codes are grouped into bands by the phase that emits them. Retired codes stay listed: numbers are never reused, so old logs remain greppable.
Syntax Errors (RAD1xxxx)¶
RAD10001: Invalid Syntax¶
This is a catch-all for syntax that doesn't match any expected pattern. The error message describes the specific problem.
Example¶
x = 5
// Wrong - can't assign to an expression
x + 1 = 6
// Correct
y = x + 1
How to Fix¶
The caret (^) in the error message points to where the parser got confused. Check
for typos, missing punctuation, or mismatched brackets around that location.
If you think this error should have a more specific code, consider filing an issue.
RAD10002: Missing Colon¶
A block-opening keyword (if, elif, else, for, while, fn,
switch, case, defer) appeared without the trailing : that
introduces its body.
Example¶
// Wrong - the 'if' header has no colon:
if true
print("hello")
Fix¶
Add the missing : at the end of the header line:
if true:
print("hello")
Every block-opening keyword needs the colon. Rad uses indentation for block structure, but the colon is what tells the parser "a block starts on the next line."
RAD10003: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10003 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10004: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10004 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10005: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10005 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10006: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10006 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10007: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10007 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10008: Reserved Keyword¶
A reserved keyword was used where an identifier was expected.
Example¶
// Wrong
args = 5
// Correct
arguments = 5
Currently args is reserved for declaring command-line arguments. Choose a
different name for your variable.
RAD10009: Unexpected Token¶
The parser found a token that doesn't make sense in the current context.
Example¶
// Wrong
x = + 5
// Correct
x = 5
The caret (^) in the error message points to the unexpected token. Look for
extra symbols, misplaced keywords, or copy-paste errors around that location.
RAD10010: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10010 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10011: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10011 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10012: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10012 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10013: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10013 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10014: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10014 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10015: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10015 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10016: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10016 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10017: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10017 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10018: Missing Indent¶
A block-opening header (if x:, for ... :, fn name(...):)
appeared but the next line wasn't indented relative to the header.
Rad uses indentation for block structure - the colon promises a
body, and the body has to live one indent level deeper.
Example¶
// Wrong - the 'if' body is at the same indent as the header:
if true:
y = 5
Fix¶
Indent the body. Convention is 4 spaces:
if true:
y = 5
Tabs and spaces both work, but stay consistent within a file - mixing them confuses the indentation tracker.
RAD10019: Retired¶
This error code is no longer in use. Tree-sitter's error recovery produces ERROR nodes for the shape this code was designed to catch, so the dispatch in error_messages.go now falls back to RAD10001 ("Invalid syntax") or RAD10009 ("Unexpected token") instead.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD10019 remain greppable.
See rad docs RAD10001 or rad docs RAD10009 for the
modern equivalents.
RAD10020: Unterminated String¶
A string literal opened with a quote but reached end-of-line (or end-of-file) without a matching closing quote.
Example¶
// Wrong - no closing quote:
message = "Hello, world
Fix¶
Close the string with the same quote character it opened with:
message = "Hello, world"
Rad supports double quotes ("), single quotes ('), and
backticks (`) - the close quote has to match the open. For
strings that genuinely span multiple lines, use backticks: they're
the multi-line string form and don't trip this check.
RAD10021: Missing Operator¶
Two values appear next to each other without an operator between them.
Example¶
// Wrong
result = 5 3
// Correct
result = 5 + 3
Add an operator (+, -, *, /, ==, and, or, etc.) between the values.
RAD10022: Keyword Misuse¶
A keyword was used in a context where it doesn't belong.
Example¶
// Wrong
else:
print("error")
// Correct
if condition:
print("if branch")
else:
print("else branch")
Keywords like else and elif must follow an if, case and default must be
inside a switch, and break and continue must be inside a loop.
Runtime Errors (RAD2xxxx)¶
RAD20000: Generic Runtime Error¶
This is a catch-all for runtime errors that don't have a more specific error code. The error message itself describes what went wrong.
Read the error message carefully. If this error seems like it should have its own code, consider filing an issue at https://github.com/amterp/rad/issues
RAD20001: Parse Int Failed¶
parse_int() couldn't convert the value to an integer.
Example¶
// These fail
parse_int("hello") // Not a number
parse_int("12.5") // Decimals not allowed
parse_int("") // Empty string
// These work
parse_int("42") // 42
parse_int("-10") // -10
How to Fix¶
Handle the error or provide a fallback:
age = parse_int(input) ?? 0
// Or with logging
age = parse_int(input) catch:
print_err("Invalid number: {age}")
exit(1)
If your input might have decimals, parse as float first:
x = int(parse_float("12.5") ?? 0) // 12
RAD20002: Parse Float Failed¶
parse_float() couldn't convert the value to a floating-point number.
Example¶
// These fail
parse_float("hello") // Not a number
parse_float("") // Empty string
parse_float("1.2.3") // Multiple decimal points
// These work
parse_float("3.14") // 3.14
parse_float("42") // 42.0
parse_float("1e10") // Scientific notation
How to Fix¶
Handle the error or provide a fallback:
value = parse_float(input) ?? 0.0
// Or with logging
value = parse_float(input) catch:
print_err("Invalid number: {value}")
exit(1)
RAD20003: File Read Error¶
Rad couldn't read the file.
Common Causes¶
- File doesn't exist - check the path
- Permission denied - you don't have read access
- Path is a directory - you tried to read a directory as a file
Example¶
content = read_file("/path/to/file.txt") catch:
print_err("Could not read file: {content}")
exit(1)
How to Fix¶
Check if the file exists before reading:
info = get_path("/path/to/file.txt")
if info.exists:
content = read_file("/path/to/file.txt")
Or handle the error with a fallback:
path = "/path/to/file.txt"
content = read_file(path) ?? ""
RAD20004: File Permission Denied¶
You don't have permission to access this file or directory.
Example¶
// Reading a protected file
content = read_file("/etc/shadow")
// Writing to a protected location
write_file("/usr/bin/myfile", "content")
How to Fix¶
- Check permissions - use
ls -lato see file permissions - Use a different location - write to a directory you control
- Handle the error:
path = "/etc/shadow"
content = read_file(path) catch:
print_err("Cannot read file: {content}")
exit(1)
RAD20005: File Does Not Exist¶
The file or directory wasn't found at the specified path.
Example¶
content = read_file("missing.txt") // File doesn't exist
How to Fix¶
Check file existence first:
filepath = "missing.txt"
info = get_path(filepath)
if info.exists:
content = read_file(filepath)
else:
print("File not found: {filepath}")
Or handle the error:
filepath = "missing.txt"
content = read_file(filepath) catch:
print_err("Could not read: {content}")
exit(1)
Common issues: typos in the path, relative paths resolving to the wrong location, or case sensitivity on some filesystems.
RAD20006: File Write Error¶
Rad couldn't write to the file.
Common Causes¶
- Permission denied - no write access to file or directory
- Disk full - no space left on device
- Directory doesn't exist - parent directory is missing
How to Fix¶
filepath = "out.txt"
content = "hello"
write_file(filepath, content) catch:
print_err("Failed to write: {filepath}")
exit(1)
Check that: 1. You have write access to the directory 2. The parent directory exists 3. There's enough disk space
RAD20007: Ambiguous Epoch¶
The timestamp value is ambiguous - Rad can't tell if it's seconds or milliseconds since epoch.
What This Means¶
Unix timestamps come in two common formats:
- Seconds (10 digits): 1700000000
- Milliseconds (13 digits): 1700000000000
Some values fall in an ambiguous range where they could reasonably be either.
How to Fix¶
Use a timestamp with a clear magnitude, or use explicit parsing that specifies the unit. Timestamps around 10 digits are typically seconds; around 13 digits are typically milliseconds.
RAD20008: Invalid Time Unit¶
The time-unit string passed to a time builtin isn't recognized.
Example¶
// Wrong - 'milliseconds' was renamed in v0.9
a = parse_epoch(1712345678000, unit="milliseconds")
// Correct
a = parse_epoch(1712345678000, unit="millis")
Valid Units¶
parse_epoch accepts:
"auto"- the default, auto-detects based on magnitude"seconds""millis""micros""nanos"
The longer aliases (milliseconds, microseconds, nanoseconds) were
removed in v0.9. See the v0.9 migration guide.
RAD20009: Invalid Timezone¶
The timezone string isn't recognized.
Example¶
// Wrong - abbreviations aren't reliable
t = now(tz="PST")
// Correct - use IANA timezone names
t = now(tz="America/Los_Angeles")
t = now(tz="Europe/London")
t = now(tz="UTC")
Valid Timezones¶
Use IANA timezone database names (format: Continent/City):
UTCAmerica/New_YorkAmerica/Los_AngelesEurope/LondonAsia/Tokyo
Full list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
RAD20010: User Input Error¶
An error occurred while reading user input from an interactive prompt - such as input(), confirm(), or the confirmation prompt shown before a shell command (confirm $\...`or--confirm-shell`).
This usually happens in one of two ways:
- stdin isn't available - for example, when running a script in a non-interactive context like a cron job or piped command.
- You aborted the prompt - pressing Ctrl-C or Esc at an interactive prompt cancels it and raises this error. (This is distinct from declining a shell confirmation by answering "n", which simply doesn't run the command.)
How to Fix¶
Check if interactive input is available:
if has_stdin():
data = read_stdin()
else:
print("No input available")
Or handle the error:
name = input("Enter name: ") catch:
name = "default"
RAD20011: Parse JSON Failed¶
parse_json() couldn't parse the input as valid JSON.
Example¶
// These fail (raw strings so {} aren't interpreted as interpolation)
parse_json(r"{invalid}") // Missing quotes around key
parse_json(r"{'key': 'value'}") // Single quotes not valid
parse_json(r"{key: value}") // Unquoted strings
// These work
parse_json(r'{"key": "value"}')
parse_json('[1, 2, 3]')
parse_json('null')
Common Issues¶
- JSON requires double quotes, not single quotes
- Object keys must be quoted strings
- No trailing commas allowed
- No comments in JSON
How to Fix¶
text = r'{"key": "value"}'
data = parse_json(text) catch:
print_err("Invalid JSON: {data}")
exit(1)
RAD20012: Internal Type Check Bug¶
This is a bug in Rad itself, not your script.
What to Do¶
Please report this at https://github.com/amterp/rad/issues with:
- The error message
- Your script (or a minimal version that reproduces it)
- Your Rad version (
rad --version) - Your operating system
RAD20013: File Walk Error¶
An error occurred while traversing a directory with find_paths().
Common Causes¶
- Permission denied - can't access a subdirectory
- Symlink loops - circular symbolic links
- Directory removed - directory deleted during traversal
How to Fix¶
dir = "."
files = find_paths(dir) catch:
print_err("Failed to scan directory: {files}")
exit(1)
Check that you have read access to all directories in the tree.
RAD20014: Mutually Exclusive Arguments¶
Two command-line arguments that can't be used together were both specified.
Example¶
args:
verbose bool
quiet bool
quiet excludes verbose
Running with --verbose --quiet triggers this error.
How to Fix¶
Use only one of the conflicting options. Run --help to see which arguments are mutually exclusive.
RAD20015: Zip Strict Length Mismatch¶
zip() was called with strict=true on lists of different lengths.
Example¶
a = [1, 2, 3]
b = ["a", "b"]
pairs = zip(a, b, strict=true) // Error: lengths 3 vs 2
How to Fix¶
Either make the lists the same length, or use non-strict mode (which truncates to the shortest):
a = [1, 2, 3]
b = ["a", "b"]
pairs = zip(a, b) // Stops at shortest list: [[1, "a"], [2, "b"]]
RAD20016: Cast Failed¶
A value couldn't be converted to the requested type - typically a
non-numeric string passed to parse_int / parse_float, or a value
of the wrong shape passed to int(...) / float(...).
Example¶
x = parse_int("hello") // "hello" isn't a number
parse_int and parse_float return an error value on failure, so the
script aborts at the use site unless the error is handled.
How to Fix¶
Handle the error with ?? (default) or catch: (block):
x = parse_int("hello") ?? 0
y = parse_float("3.14") ?? 0.0
Or branch on whether the input looks parseable before attempting:
raw = "hello"
n = parse_int(raw) catch:
print_err("not a number: {raw}")
exit(1)
RAD20017: Number Out of Range¶
A numeric value is outside the valid range accepted by an operation.
Example¶
x = round(3.14159, -2) // Error: precision must be non-negative
How to Fix¶
Check that values are within the bounds the operation accepts. For example,
round requires a non-negative precision, to_json a non-negative indent, and
range a non-zero step.
(An integer literal that doesn't fit in an int64 is a separate, compile-time error - see RAD40015.)
RAD20018: Empty List¶
An operation that requires at least one element was called on an empty list.
Example¶
items = []
first = items[0] // No first element
max_val = max(items) // Nothing to compare
How to Fix¶
Check the list length first:
items = []
if len(items) > 0:
first = items[0]
else:
first = "default"
Or use a ternary:
items = []
first = len(items) > 0 ? items[0] : "default"
RAD20019: Argument Constraint Violated¶
A command-line argument didn't meet its declared constraint.
Example¶
args:
level str
level enum ["low", "medium", "high"]
Running with --level ultra fails because "ultra" isn't in the allowed values.
Constraint Types¶
- Enum - value must be one of the specified options
- Regex - value must match a pattern
- Range - numeric value must be within bounds
How to Fix¶
Run --help to see valid values. Constraints are case-sensitive.
RAD20020: FID Generator Error¶
Rad failed to generate a unique identifier (FID). This is a system-level issue, not a problem with your script.
How to Fix¶
Retry the operation. If the error persists, file a bug report at: https://github.com/amterp/rad/issues
RAD20021: Decode Error¶
The input couldn't be decoded because it's not valid for the specified encoding.
Example¶
// Invalid base64 - contains characters not in base64 alphabet
decode_base64("not valid base64!!!")
// Correct
decoded = decode_base64("SGVsbG8gV29ybGQ=")
How to Fix¶
Verify the data matches the encoding you're using. If the data comes from an external source, handle potential errors:
data = "SGVsbG8gV29ybGQ="
decoded = decode_base64(data) catch:
print_err("Invalid base64 data")
exit(1)
RAD20022: Stash ID Not Found¶
A stash function was called, but the script doesn't declare a stash ID.
Stashes scope persistent data per-script, so Rad needs to know which
script owns the data. Set the stash ID with @stash_id in the file
header.
Example¶
// Wrong: no @stash_id header, so any stash call fails at runtime.
result = load_stash_file("config.json", r'{"theme": "dark"}')
How to Fix¶
Add @stash_id to the file header:
---
@stash_id = my_script
---
// Now stash calls work. load_stash_file creates the file with the
// default content if it's not there yet. Pair with write_stash_file
// to update.
result = load_stash_file("config.json", r'{"theme": "dark"}')
To see existing stashes, run rad stash list.
See also: Stashes guide.
RAD20023: Invalid Sleep Duration¶
The sleep() function received a duration it doesn't understand.
Example¶
// Wrong
sleep("abc") // not a valid duration
sleep("5x") // unknown unit
// Correct
sleep(1.5) // 1.5 seconds (numbers are seconds)
sleep("500ms") // 500 milliseconds
sleep("2s") // 2 seconds
sleep("1m") // 1 minute
sleep("1h") // 1 hour
Valid Units¶
| Unit | Meaning |
|---|---|
| ms | milliseconds |
| s | seconds |
| m | minutes |
| h | hours |
Numbers without units are treated as seconds.
RAD20024: Invalid Regular Expression¶
The regex pattern has a syntax error.
Example¶
// Wrong: unclosed bracket
matches("hello", "[invalid")
// Correct
matches("hello", "[a-z]+")
matches("test123", "\\d+")
Common Mistakes¶
- Unclosed
[or( - Quantifiers (
*,+,?) with nothing before them:"*abc"should be".*abc" - Missing escape: use
\\dfor digits,\\.for literal dot
Rad uses Go's regex syntax. Test complex patterns with an online regex tester.
RAD20025: Colorize Value Not in Enum¶
A colorization function received a value that wasn't in its predefined set.
When you configure color-based formatting (such as in table columns), you define which values map to which colors. This error means the data contained a value not in that mapping.
How to Fix¶
Either add the missing value to your color mapping, or ensure your data only contains expected values before colorizing.
RAD20026: Stdin Read Error¶
Something went wrong while reading from standard input - the stream may have been closed or corrupted.
How to Fix¶
Check whether stdin is available before reading:
if has_stdin():
data = read_stdin()
else:
print_err("No input provided")
exit(1)
RAD20027: Invalid Check Duration¶
An invalid duration was specified for an internal operation.
This error is used internally for testing. If you encounter it unexpectedly, please file an issue at https://github.com/amterp/rad/issues.
RAD20028: Undefined Identifier¶
You're referring to an identifier (a variable or function) that hasn't been defined yet.
Example¶
username = "alice"
print(usernme) // Error: typo - 'usernme' doesn't exist
Common Causes¶
- Typos - Check the spelling. Rad suggests similar names if it finds any.
- Wrong order - The variable must be assigned before you use it.
- Scope - Variables inside functions aren't visible outside:
fn setup():
config = "value" // only exists inside setup()
setup()
print(config) // Error: config is not defined here
Note¶
Rad is case-sensitive: MyVar and myvar are different variables.
RAD20029: Index Out of Bounds¶
You tried to access an index that doesn't exist in the list or string.
Example¶
items = ["a", "b", "c"] // valid indices: 0, 1, 2
print(items[3]) // Error: index 3 out of bounds
Lists are 0-indexed, so a list with 3 elements has indices 0, 1, and 2.
How to Fix¶
Check the length before accessing:
items = ["a", "b", "c"]
index = 3
if len(items) > index:
print(items[index])
Negative indices work too: -1 is the last element, -2 is second-to-last. But going past the start (e.g., items[-10] on a 3-element list) still causes this error.
RAD20030: Break Outside Loop¶
break can only be used inside a loop. It exits the loop early.
Example¶
x = 10
// Wrong
if x > 5:
break // not inside a loop
// Correct
for i in range(10):
if i > 5:
break
print(i)
Alternatives¶
- To exit a function: use
return - To exit the script: use
exit()
See also: RAD20031 (Continue Outside Loop)
RAD20031: Continue Outside Loop¶
continue can only be used inside a loop. It skips to the next iteration.
Example¶
x = 10
// Wrong
if x < 20:
continue // not inside a loop
// Correct
for i in range(10):
if i % 2 == 0:
continue // skip even numbers
print(i)
See also: RAD20030 (Break Outside Loop)
RAD20032: Not Iterable¶
You tried to use for on something that can't be iterated.
Example¶
// Wrong: can't iterate over an int
for x in 42:
print(x)
// Correct: iterate over a list, string, map, or range
for x in [1, 2, 3]:
print(x)
Iterable Types¶
| Type | Iterates over |
|---|---|
| List | elements |
| String | characters |
| Map | keys |
| Range | numbers |
Integers, floats, and booleans are not iterable.
RAD20033: Unpack Mismatch¶
The number of variables doesn't match the number of values being unpacked.
Example¶
// Wrong: 3 values, 2 variables
a, b = [1, 2, 3]
// Correct
a, b, c = [1, 2, 3]
How to Fix¶
Either adjust the variable count, or use indexing if you only need some values:
items = [1, 2, 3]
first = items[0]
last = items[-1]
RAD20034: Switch No Match¶
The switch value didn't match any case, and there's no default.
Example¶
status = "pending"
switch status:
case "active":
print("Active")
case "inactive":
print("Inactive")
// Error if status is something else like "pending"
How to Fix¶
Add a default case:
status = "pending"
switch status:
case "active":
print("Active")
case "inactive":
print("Inactive")
default:
print("Unknown status: {status}")
RAD20035: Switch Multiple Match¶
The value matched more than one case, and Rad refuses to silently prefer one over the other.
Example¶
name = "alice"
switch name:
case "alice" -> print("ALICE")
case "bob" -> print("BOB")
case "charlie", name -> print("CHARLIE")
// Error: "alice" matches both the first and third cases
The third case binds name as one of its match keys, which makes any
name value match - so an input of "alice" overlaps with the first
case as well.
How to Fix¶
Make the cases mutually exclusive, or restructure as an if/else
if chain if you need guard-style logic:
name = "alice"
if name == "alice":
print("ALICE")
else if name == "bob":
print("BOB")
else:
print("CHARLIE")
When the cases are constant literals, the static checker catches the overlap as RAD40012 (Unreachable Case) before runtime. This runtime error remains the safety net for overlaps that only show up once the discriminant value is known.
RAD20036: Division by Zero¶
You can't divide or modulo by zero.
Example¶
x = 10 / 0 // Error
y = 10 % 0 // Error
How to Fix¶
Check the divisor before using it:
value = 10
divisor = 0
if divisor != 0:
result = value / divisor
else:
result = 0 // or handle appropriately
RAD20037: Negative Index¶
A negative index was used where it's not allowed.
Rad generally supports negative indexing (-1 for the last element), so this error is rare. If you encounter it unexpectedly, please report it at https://github.com/amterp/rad/issues.
RAD20038: Void Value¶
You tried to use the result of a function that doesn't return anything.
Example¶
fn greet():
print("Hello!")
// no return statement
result = greet() // Error: greet() returns void
How to Fix¶
Either add a return value to the function:
fn greet():
print("Hello!")
return true
Or don't try to capture the result:
fn greet():
print("Hello!")
greet() // just call it
RAD20039: Unsupported Operation¶
The operation you tried isn't supported for this type.
This is a general error - the message will tell you which operation failed and on what type. Check that you're using the right types and operators for what you're trying to do.
RAD20040: Retired¶
This error code is no longer in use. The assert() built-in it was
designed to flag was never implemented; this doc previously described
a planned feature that didn't land.
The number stays reserved per the tombstone rule - we never reuse retired codes, so old logs that mention RAD20040 remain greppable.
For runtime invariant checks today, use an explicit if + exit():
items = [1, 2, 3]
if len(items) == 0:
print_err("items must not be empty")
exit(1)
RAD20041: Key Not Found¶
You tried to access a map key that doesn't exist.
Example¶
data = {"name": "Alice", "age": 30}
print(data["email"]) // Error: 'email' not found
How to Fix¶
Check first, or use a default:
data = {"name": "Alice", "age": 30}
// Check first
if "email" in data:
print(data["email"])
// Or use the ?? fallback operator
email = data["email"] ?? "unknown"
RAD20042: Internal Bug¶
This is a bug in Rad, not your script. Something went wrong inside the interpreter.
What to Do¶
Please report this at https://github.com/amterp/rad/issues with:
- The error message and stack trace
- Your script (or a minimal version that reproduces it)
- Your Rad version (rad --version)
- Your OS
As a workaround, try restructuring the problematic code - sometimes a different approach avoids the bug.
RAD20043: Failed to Parse Duration¶
A string passed to parse_duration could not be interpreted as a valid duration.
Example¶
parse_duration("invalid!") // Error: failed to parse
parse_duration("5m23s") // OK: 5 minutes 23 seconds
parse_duration("1d12h") // OK: 1 day 12 hours
Valid Format¶
Duration strings support these suffixes: w, d, h, m, s, ms, us/µs, ns.
Combine them like "1w2d" or "5m30s". Spaces are allowed ("5m 30s").
A leading - negates the entire duration.
How to Fix¶
- Ensure the string contains valid duration suffixes
- Check for typos in the unit suffixes
- Use
catchto handle the error if the input is dynamic
RAD20044: Failed to Parse Date¶
A string passed to parse_date could not be interpreted as a valid date.
Example¶
parse_date("not-a-date") // Error: unrecognized format
parse_date("2026-03-22") // OK: ISO date
parse_date("2026-03-22T14:30:00Z") // OK: ISO datetime with timezone
parse_date("22/03/2026", format="DD/MM/YYYY") // OK: custom format
Auto-Detected Formats¶
When no format is specified, parse_date tries these formats:
YYYY-MM-DDTHH:mm:ssZor...+HH:MM(RFC 3339, with optional fractional seconds)YYYY-MM-DD HH:mm:ss+HH:MM(space-separated with timezone offset, with optional fractional seconds)YYYY-MM-DDTHH:mm:ss(ISO datetime, with optional fractional seconds)YYYY-MM-DD HH:mm:ss(space-separated, with optional fractional seconds)YYYY-MM-DD(date only)
Format Tokens¶
When using the format parameter, these tokens are available:
| Token | Meaning | Example |
|---|---|---|
YYYY |
4-digit year | 2026 |
MM |
2-digit month | 03 |
DD |
2-digit day | 22 |
HH |
2-digit hour (24h) | 14 |
mm |
2-digit minute | 30 |
ss |
2-digit second | 00 |
Note: MM (uppercase) is month, mm (lowercase) is minute. Mixing these
up will produce wrong results or parse errors.
Format tokens are replaced wherever they appear in the format string,
including inside other text. Use format strings that contain only tokens
and separator characters (e.g. DD/MM/YYYY, YYYY-MM-DD HH:mm:ss).
How to Fix¶
- Check that your date string matches one of the auto-detected formats, or provide an explicit
format - Ensure the format tokens match the structure of your date string
- Use
catchto handle the error if the input is dynamic
Type Errors (RAD3xxxx)¶
RAD30001: Type Mismatch¶
A value of one type was used where a different type was expected.
Example¶
fn double(n: int) -> int:
return n * 2
x = "42"
double(x) // Error: expected int, got string
How to Fix¶
Use the right type, or fix the function/variable annotation:
fn double(n: int) -> int:
return n * 2
x = 42 // use int directly
double(x)
If you must convert from another type, note that the conversion
functions (int, float, etc.) can fail and return T|error, so
you need to handle the error case before using the result:
x = "42"
n = int(x) catch:
print_err("Not a number: {x}")
exit(1)
print(n)
Type Conversion Functions¶
| Function | Description |
|---|---|
int(value) |
Convert to integer (or error) |
float(value) |
Convert to float (or error) |
str(value) |
Convert to string |
bool(value) |
Convert to boolean |
RAD30002: Invalid Type for Operation¶
An operator was used with types that don't support that operation.
Example¶
// Wrong: can't add int to string
x = "count: " + 5
// Correct: use interpolation (preferred)
x = "count: {5}"
// Correct: explicit conversion
x = "count: " + str(5)
Supported Type Combinations¶
| Operator | Works with |
|---|---|
+ |
int+int, float+float, str+str, str+error, error+str, error+error, list+list |
-, *, / |
int, float |
% |
int |
==, != |
any types |
<, >, <=, >= |
int, float, str |
and, or |
any (uses truthiness) |
How to Fix¶
Use interpolation to mix types in strings - it handles any type automatically:
"Value: {x}". Alternatively, convert types explicitly with int(), float(), or str().
v0.9 Migration Note: The
+operator no longer coerces types. If you're seeing this error after upgrading, see the v0.9 migration guide.
RAD30003: Cannot Format¶
A value couldn't be formatted with the given format specifier.
Example¶
x = "hello"
print("{x:.2}") // Error: precision (.2) needs a number, got string
// Fix: use a compatible specifier or convert first
print("{x}") // Default works for any type
print("{parse_int('42'):,}") // Convert before formatting
Format Specifiers¶
| Specifier | Requires | Example |
|---|---|---|
{x} |
any type | Default formatting |
{x:10} |
any type | Right-align, width 10 |
{x:<10} |
any type | Left-align, width 10 |
{x:*>10} |
any type | Fill with *, right-align |
{x:.<10} |
any type | Fill with ., left-align |
{x:05} |
any type | Zero-pad shorthand, width 5 |
{x:<05} |
any type | Zero-pad, left-align |
{x:0>5} |
any type | Explicit zero fill (any type) |
{x:.2} |
number | Precision (2 decimals) |
{x:,} |
number | Thousands separator |
{x:010,} |
number | Zero-pad + thousands |
The zero-pad shorthand ({x:05}) works on any type. For numbers,
it is sign-aware (negative signs placed before zeros). For other
types, it simply prepends zeros (same as {x:0>5}).
RAD30004: Cannot Index¶
Indexing was attempted on a type that doesn't support it.
Example¶
// Wrong: can't index an integer
x = 42
print(x[0])
// Correct: index lists, strings, or maps
items = [1, 2, 3]
print(items[0]) // 1
text = "hello"
print(text[0]) // "h"
data = {"a": 1}
print(data["a"]) // 1
Indexable Types¶
| Type | Syntax |
|---|---|
| Lists | items[0], items[-1], items[1:3] |
| Strings | text[0], text[-1], text[1:3] |
| Maps | data["key"] |
To index a number's digits, convert it first: str(12345)[0] gives "1".
RAD30005: Cannot Assign¶
Assignment was attempted to something that can't be assigned to.
Example¶
items = [1, 2, 3]
data = {"k": "value"}
// Wrong
5 = x
get_value() = 5
// Right: assign to variables or index expressions
x = 5
items[0] = 10
data["key"] = "value"
a, b = [1, 2]
The left side of = must be a variable, index expression, or unpacking pattern.
RAD30006: Invalid Argument Type¶
A function received an argument of the wrong type.
Example¶
// Wrong: len() expects a collection
len(42)
// Correct
len([1, 2, 3]) // 3
len("hello") // 5
The error message tells you what type was expected. Convert with int(),
str(), list(), etc. if needed.
RAD30007: Wrong Argument Count¶
A function was called with too few or too many arguments.
Example¶
fn add(a, b):
return a + b
add(5) // Error: missing argument 'b'
add(1, 2, 3) // Error: too many arguments
How to Fix¶
Check the function signature. Use rad docs reference/functions to see the
expected arguments for built-in functions.
fn add(a, b):
return a + b
add(5, 10) // Correct: two arguments
Named arguments can help clarify which is which:
fn process(input, output):
print("Processing {input} -> {output}")
value = "in.txt"
dest = "out.txt"
process(input=value, output=dest)
RAD30008: Cannot Compare¶
Two values cannot be compared with the given comparison operator.
This error code is reserved and may not currently appear in practice. Most types can be compared for equality, and ordering comparisons work on compatible types.
If you encounter this error, please report it: https://github.com/amterp/rad/issues
RAD30009: Cannot Convert¶
A type conversion is not possible between the given types.
This error code is reserved and may not currently appear in practice. Most
conversions use dedicated functions like int(), str(), etc. which have
their own error handling.
If you encounter this error, please report it: https://github.com/amterp/rad/issues
RAD30010: Collection Element Mismatch¶
A list element or map value was assigned a value that doesn't match
the collection's declared element/value type. Indexed assignment
(xs[i] = v or m[k] = v) is the only static mutation surface on
typed collections today, so this is where the gap shows up.
Example¶
xs: int[] = [1, 2, 3]
xs[0] = "wrong" // Error: 'str' not assignable to element 'int'
m: { str: int } = { "a": 1 }
m["b"] = "nope" // Error: 'str' not assignable to value 'int'
How to Fix¶
Use a value of the declared element type:
xs: int[] = [1, 2, 3]
m: { str: int } = { "a": 1 }
xs[0] = 99 // int into int[]
m["b"] = 2 // int into { str: int }
Or, if the collection genuinely holds mixed values, widen the declared type so the assignment lands in scope:
xs: (int|str)[] = [1, "two", 3]
xs[0] = "wrong" // ok: 'str' is in the (int|str) element type
When the Check Skips¶
The check is opt-in via annotation. Untyped locals (xs = [1, 2])
infer a non-narrow element type and don't trigger this diagnostic.
Containers typed as the open list or map (no element type) also
skip - they're already "any element goes."
RAD30011: Unhandled Fallible Call¶
Calling a function that can fail (returns ... | error) without handling the
error case.
port_str = "8080"
port = parse_int(port_str) // RAD30011: this call can fail; the error isn't handled
print(port + 1)
Why this happens¶
Functions like parse_int, parse_float, and parse_json return a union of a
success type and error (e.g. int | error). At runtime, if the call fails and
nothing handles the error, the script halts. The checker points this out so the
failure path is a deliberate choice rather than an accident.
Note this is a hint, not an error: the script still runs, and succeeds whenever
the call succeeds. The success type flows on (port above is int), so later
uses type-check normally.
How to fix it¶
Handle the error with catch to supply a fallback value:
port_str = "8080"
port = parse_int(port_str) catch 8080
print(port + 1)
Or use ?? for the same effect more tersely:
port_str = "8080"
port = parse_int(port_str) ?? 8080
print(port + 1)
Use a catch block when the recovery needs more than a single fallback value:
port_str = "8080"
port = parse_int(port_str) catch:
print("invalid port, using default")
yield 8080
print(port + 1)
Validation & Lint Errors (RAD4xxxx)¶
RAD40001: Scientific Notation Not a Whole Number¶
Scientific notation was used where an integer is required, but the result has a fractional part.
Example¶
args:
count int = 1.5e1 # Error: 15.0 is not a whole number
These produce whole numbers and are fine:
args:
count int = 1e3 # 1000
batch int = 2e2 # 200
What This Means¶
Scientific notation like 1e3 means 1 x 10^3 = 1000. When used where an integer
is expected, the result must be a whole number. 1.5e1 produces 15.0, which
has an implicit decimal part.
Use plain integers (1000) or ensure the notation produces an exact integer.
RAD40002: Function Shadows Argument¶
A function has the same name as a command-line argument.
Example¶
args:
count int
fn count(): // Error: shadows the 'count' argument
return 5
Rad hoists function definitions to the top of the scope, so this would hide the argument value and make it inaccessible.
How to Fix¶
Rename either the function or the argument:
args:
count int
fn get_count(): // Renamed function
return count
RAD40003: Unknown Function¶
A function was called that hasn't been defined.
Example¶
greet("Alice") // Error: 'greet' is not defined
// Fix: define the function first
fn greet(name):
print("Hello, {name}!")
greet("Alice")
How to Fix¶
- Check spelling - Rad suggests similar names if any exist
- Define the function - Add the function definition before the call
- Check scope - Ensure the function is visible where you're calling it
Built-in functions like print, len, and int are always available.
RAD40004: Return Outside Function¶
A return statement appeared outside of a function body.
Example¶
// Wrong
x = 10
return x // Error: not inside a function
// Correct
fn get_value():
return 10
result = get_value()
How to Fix¶
- To end the script early, use
exit():exit(1)for failure,exit(0)for success - Check indentation - The return might be accidentally de-indented outside the function body
- Wrap in a function if you need return semantics at top level
RAD40005: Yield Outside Switch Case¶
A yield statement appeared outside of a switch-case block. yield is
how a multi-statement switch arm returns its result to the surrounding
switch expression.
Example¶
// Wrong - yield at top level
yield 5 // Error: not inside a switch case
// Correct - yield inside a switch-case block
value = "a"
result = switch value:
case "a":
x = 10
yield x * 2
case "b" -> 20
default -> 0
In single-expression switch arms you use -> instead; yield is only
needed when an arm has multiple statements and wants to return a value.
RAD40006: Invalid Assignment Target¶
The left side of an assignment isn't something that can be assigned to.
Example¶
items = [1, 2, 3]
data = {"k": "v"}
// Wrong
5 = x
a + b = 10
// Valid assignment targets
x = 5 // Variable
items[0] = 10 // List element
data["key"] = "v" // Map entry
a, b = [1, 2] // Unpacking
x += 1 // Compound
The left side of = must be a variable, index expression, or unpacking pattern.
RAD40007: Rad Option Has No Effect¶
A rad block option was used in a context where it has no effect.
Example¶
// 'insecure' and 'quiet' only apply to URL sources, so using them
// on a sourceless rad block is pointless.
rad:
insecure // Warning: no URL to apply this to
quiet // Warning: no URL to apply this to
fields Name
// 'noprint' has no effect without a source because sourceless rad
// blocks use a save/restore pattern -- mutations aren't preserved.
rad:
noprint // Warning: mutations aren't preserved anyway
fields Name
Note: when a source expression is provided (e.g. rad data:), the source
could resolve to either a URL or a list/map at runtime, so no static
warning is emitted -- both code paths are legitimate.
How to Fix¶
- Remove the option if it's not needed
- Use a URL source if you need
insecureorquiet - Add a source if you need
noprintto suppress printing
RAD40008: Deprecated Block Keyword¶
The request and display block keywords have been removed in v0.9.
All rad block variants now use the unified rad keyword.
Migration¶
Replace request and display with rad:
data = [{"name": "Alice", "age": 30}]
Name = json[].name
Age = json[].age
// Before (no longer works)
request "https://api.example.com/data":
fields Name, Age
display data:
fields Name, Age
// After
rad "https://api.example.com/data":
noprint
fields Name, Age
rad data:
fields Name, Age
Important: request blocks never printed a table - they only fetched
data and populated fields. The unified rad block prints by default, so
add noprint when migrating from request if you don't want table output.
The rad keyword now handles all source types:
- URL source:
rad "https://...":(replacesrequest- addnoprintto keep old behavior) - List/map source:
rad myData:(replacesdisplay) - No source:
rad:(replacesdisplaywith no argument)
See the migration guide for full details.
RAD40009: Duplicate Parameter¶
A function or lambda has two parameters with the same name. Parameter names must be unique within a single parameter list so the body can refer to each one unambiguously.
Example¶
// This is an error: 'x' is declared twice.
fn add(x, x):
return x + x
Fix¶
Rename one of the parameters so each binding has a distinct name:
fn add(a, b):
return a + b
Shadowing an outer-scope name with a parameter is fine - that's a common pattern. This error only fires when two parameters of the same function or lambda collide with each other.
RAD40010: Non-Exhaustive Switch¶
A switch over a closed type (today: string-enum) doesn't cover
every value in the type, and there's no default arm to catch the
rest. At runtime the unmatched values would fail with
ErrSwitchNoMatch (RAD20034); the static check surfaces the gap
before the script runs.
Example¶
// 'c' is not covered.
fn render(name: ["a", "b", "c"]):
switch name:
case "a":
print("AAA")
case "b":
print("BBB")
Fix¶
Either add a case for the missing value:
fn render(name: ["a", "b", "c"]):
switch name:
case "a":
print("AAA")
case "b":
print("BBB")
case "c":
print("CCC")
...or add a default arm to catch the rest:
fn render(name: ["a", "b", "c"]):
switch name:
case "a":
print("AAA")
default:
print("other")
Open-typed discriminants (plain str, int, etc.) never trigger
this warning. Exhaustiveness only applies when the static type is a
finite set the checker can enumerate.
RAD40011: Duplicate Typed Declaration¶
A name was declared with a type annotation (x: T = ...) when x
was already declared in the same scope. The declared type is part of
the binding's contract: re-declaring would either silently change
the type out from under earlier assignments or duplicate information
the reader has to reconcile. Either way it's a refactor hazard, so
the static checker rejects it.
Example¶
// This is an error: 'bb' was already declared on line 1.
bb: int = 5
bb: str = "hi"
Same-type re-declarations are flagged for the same reason - they're almost always leftover scaffolding from an edit:
// Also an error.
bb: int = 5
bb: int = 6
Fix¶
If you want to reassign, drop the annotation. The original declared type still applies, and the checker enforces it:
bb: int = 5
bb = 6
If you really meant to introduce a new variable, give it a fresh name. Rad doesn't currently support intentional re-declaration in the same scope.
RAD40012: Unreachable Case¶
A switch case key matches a value that was already matched by an
earlier arm. The later arm can never be reached at runtime, so it's
almost always dead code - either a copy-paste mistake or leftover
scaffolding from an edit.
Example¶
name = "a"
// This is an error: 'a' is matched by the first case.
switch name:
case "a":
print("first")
case "a":
print("second")
Multi-key cases get the same treatment - any single key that collides with an earlier arm is flagged:
name = "b"
// 'b' on line 4 is unreachable.
switch name:
case "a", "b":
print("first")
case "b", "c":
print("second")
Fix¶
Remove the redundant key. If you wanted both branches to execute the same body, merge them:
name = "a"
switch name:
case "a", "b":
print("matched")
Or, if the second arm was supposed to match a different value, correct the key:
name = "a"
switch name:
case "a":
print("first")
case "b":
print("second")
Only static literals are checked (strings, ints, floats, bools). Cases keyed by variables, function calls, or other expressions are left alone - the analyzer can't prove they collide.
RAD40013: Case Key Not In Discriminant Type¶
A switch case key has a value that the discriminant can never
hold. The arm is unreachable not because of an earlier case
(that's RAD40012), but because the discriminant's
type statically forbids the value.
This typically surfaces after a refactor: an enum variant gets renamed and the old spelling lingers in a switch.
Example¶
fn classify(name: ["a", "b", "c"]):
// 'wat' is not in the closed type ["a", "b", "c"];
// `name` can never equal it.
switch name:
case "a":
print("first")
case "b":
print("second")
case "c":
print("third")
case "wat": // RAD40013
print("unreachable")
Fix¶
Drop the stale arm:
fn classify(name: ["a", "b", "c"]):
switch name:
case "a":
print("first")
case "b":
print("second")
case "c":
print("third")
Or correct the key to one of the type's allowed values:
fn classify(name: ["a", "b", "c"]):
switch name:
case "a":
print("first")
case "b":
print("second")
case "c":
print("third")
When the check fires¶
The check only fires when the discriminant has a closed value
set the analyzer can enumerate. Today that means string-enum
types (e.g. ["a", "b", "c"]). Open str / int discriminants
have no enumerable domain, so the check stays silent.
Only static literal keys are checked. Cases keyed by identifiers, calls, or other expressions are left alone - the analyzer can't prove they fall outside the domain.
RAD40014: Unknown Map Key¶
A map access reads a key that the map's type doesn't declare. The access is guaranteed to fail at runtime (RAD20041, key not found), so the analyzer flags it statically.
This fires only when the receiver has a closed map shape the
analyzer can enumerate - typically a builtin's typed-map return
value (e.g. get_path) or an explicitly annotated struct-typed
map. Untyped map values have no declared keys, so the check
stays silent for them.
Example¶
gp = get_path("README.md")
print(gp.full_path) // ok: 'full_path' is a declared key
print(gp.fjull_path) // RAD40014: typo - no such key
Fix¶
Use a declared key:
gp = get_path("README.md")
print(gp.full_path)
If the key you want is optional (marked ? in the type, e.g.
get_path's size_bytes?), it's a valid key and won't fire this -
but it may be absent at runtime. Guard the access so a missing key
doesn't error (RAD20041).
When the check fires¶
Only reads are checked. Writing a key that isn't declared
(gp.extra = 1) is allowed - it adds the key at runtime - so it
does not fire.
Both dot access (gp.full_path) and string-literal index access
(gp["full_path"]) are checked. Dynamic keys (gp[k]) and keys
built from interpolation can't be resolved statically, so they're
left alone.
Optional keys (declared with ?, e.g. get_path's size_bytes?)
are considered valid keys: accessing one resolves its type and does
not fire here, even though the key may be absent at runtime.
RAD40015: Integer Literal Out of Range¶
An integer literal was written whose value does not fit in a signed 64-bit integer. Rad integers are 64-bit, so a literal must be in the range -9223372036854775808 to 9223372036854775807.
Example¶
x = 9223372036854775808 // Error: one past the maximum int64
These are fine:
biggest = 9223372036854775807 // the maximum int64
million = 1_000_000 // underscores are allowed as digit separators
print(biggest, million)
What This Means¶
The literal's magnitude exceeds what an int can hold. Note that the most
negative value cannot be written directly as -9223372036854775808, because the
literal 9223372036854775808 is parsed (and range-checked) on its own before the
unary minus applies.
Write the minimum value as -9223372036854775807 - 1, or use a float if you
need to represent larger magnitudes (with reduced precision).