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.
RAD10023: '#' Comment¶
A # was used to start a comment, but Rad comments use //.
Example¶
// Wrong
# compute the total
// Correct
// compute the total
The # habit usually comes from Python or shell. In Rad, # starts a comment
in exactly two places: the shebang line (#!/usr/bin/env rad) and inside
args blocks, where # ... after an argument declaration documents that
argument. Everywhere else, use //.
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 rad docs migrations/v0.9.
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
RAD20045: Invalid Shell Command Value¶
A value used as a shell command, or interpolated into one, has no form Rad can safely pass to a program.
Inside a shell command, the text you write is shell and the interpolations are
data. Rad quotes each interpolated value so it arrives as exactly one argument.
That only works for values with an obvious one-argument spelling: strings,
numbers and booleans. A map has none, and a null has one that is silently wrong
- it would become the four-character word null.
Examples¶
tag = null
$`git tag {tag}` // Error: nothing sensible to pass
config = { "depth": 2 }
$`git clone {config}` // Error: a map is not an argument
A list is allowed - it expands to one argument per element - but it has to stand alone as its own argument, because there's no single obvious meaning for gluing several arguments onto a prefix:
files = ["a.txt", "b.txt"]
$`rm {files}` // Fine: rm a.txt b.txt
$`rm --file={files}` // Error: which element gets the prefix?
The same rules apply when the whole command is a list:
$["git", "commit", "-m", null] // Error
$[] // Error: no program to run
How to Fix¶
Give a null an explicit fallback, or leave the argument out entirely:
tag = null
$`git tag {tag ?? "untagged"}`
Build up the arguments as a list when some are conditional. Each element is one argument, so nothing needs quoting:
message = "fix the parser"
cmd = ["git", "commit"]
if message:
cmd += ["-m", message]
$cmd
Join a list yourself when you really do want one argument:
files = ["a.txt", "b.txt"]
$`tar -czf out.tgz {files}` // three arguments
$`echo --files={files.join(",")}` // one argument
Why Rad Works This Way¶
Bash's defining hazard is that a value carrying a space, a quote or a $ stops
being a value and becomes syntax. Rad removes that by quoting interpolations for
you - but quoting only has an answer for things that are one argument. Rather
than invent a rendering for the rest and let it fail silently at runtime, Rad
asks you to say what you meant.
See Also¶
rad docs guide/shell-commands- the three command forms and when to use each
RAD20046: Interactive Input Required¶
The script asks the user something - via input(), confirm(), pick(), multipick(), or a confirm-gated shell command - but there is no terminal to ask at. Rad stops before running any of the script and tells you what it would have asked.
Rad looks for a terminal in two places: standard input, and /dev/tty (the terminal attached to your session, which works even when stdin is a pipe or a here-string). Both being unavailable means nothing interactive can happen: a CI job, a cron run, or an AI agent's tool call.
Nothing in the script has executed at this point. That is deliberate - discovering a prompt halfway through would leave you reasoning about a half-finished run before you could retry.
Rad exits 7 here, so a wrapper can tell "this run needs input" apart from "this run failed" without reading the message.
How to Fix¶
Answer each prompt up front with --reply, keyed by the line it sits on. Rad prints your own command back with a --reply per prompt: keys in place, answers left blank.
rad deploy.rad prod --reply '5:<yes|no>' --reply '6:<value>' --reply '7:<option>'
Replace the blanks and run it. Rad never fills them in - whether you mean yes, or which option you want, isn't in the script for it to read:
rad deploy.rad prod --reply 5:yes --reply 6:'shipped it' --reply 7:web-1
Answers are matched by kind, which is what the blank names. Nothing needs escaping except a multipick:
| Prompt | Blank | Answer |
|---|---|---|
confirm, confirm $\...`||yes/no(alsoy,n,true,false`) |
||
input |
<value> |
the rest of the value, taken verbatim |
pick, pick_kv, pick_from_resource |
<option> |
one option, matched exactly |
multipick |
<option,...> |
comma-separated; \, for a literal comma, \\ for a backslash |
Only the first colon separates the key from the value, so --reply 6:https://example.com needs no quoting. A blank left unreplaced is taken literally: confirm, pick, and multipick reject it, and input answers with the text <value>.
A multipick answer must name each option at most once and satisfy the prompt's min and max. Give an empty value (--reply 3:) to select nothing.
Repeating a Prompt¶
A prompt inside a loop runs more than once. Repeat the key to answer each pass in order:
rad cleanup.rad --reply 12:yes --reply 12:no
Answers are queued per line rather than globally, so adding a prompt earlier in a script never silently re-targets a later answer. Running out mid-loop stops the script rather than reusing the last answer.
A prompt in a function called from several places repeats too, and then one key stands for several different questions:
fn ask(label):
return input("Enter {label}: ")
host = ask("hostname")
user = ask("username")
print("{user}@{host}")
Answers bind in the order the calls run. Where rad can account for every execution it lists them, so that order is readable without opening the file:
deploy.rad:2 input may run more than once - repeat --reply per run
reached from:
4 host = ask("hostname")
5 user = ask("username")
It says nothing where it cannot account for them all - a call inside a loop, a function passed around as a value, a caller that itself runs more than once, or a recursive function. A list missing one call would read exactly like a complete one.
Rad cannot count the passes for you either way; that depends on the script's own data. Where stopping partway would cost you something, read the script and count before you answer.
Prompts You Don't Expect To Reach¶
If a prompt sits on a branch this run won't take, say so instead of inventing a value:
rad deploy.rad --dry-run --reply-na 20
If the script reaches it anyway, rad fails cleanly (see RAD20047) rather than acting on a guess. Use this for a pick given a filter too, since a filtered pick often narrows to a single option and never asks. Where the options and the filter are both literal, rad works out the survivor itself and writes the --reply-na for you; where either is computed while the script runs, that call is yours to make.
Secret Inputs¶
A secret input cannot be answered this way at all. Command-line arguments are visible to other processes on the machine, so a password there would leak. Run the script where a terminal is available, or use --reply-na if this run won't reach it. Rad refuses the answer whether secret is a literal or computed while the script runs, though only a literal can be reported here rather than at the prompt.
Interactive Functions Used As Values¶
One shape takes no --reply: an interactive function passed around as a value rather than called.
ask = pick
answer = ask(["dev", "prod"])
Answers are keyed by the position of the call, and rad can't see where such a value ends up or how often it runs, so there is nothing for a value to attach to. Use --reply-na on its line to assert this run never invokes it - which is the usual answer for a handler map whose interactive entry isn't the one selected. If that assertion is wrong, the script stops at the prompt (see RAD20047) rather than guessing.
Scripts That Disable Global Options¶
A script setting @enable_global_options = 0 removes --reply along with every other global flag, so its prompts can't be answered from the command line. Rad says so rather than suggesting a command the script would reject. Run it where a terminal is available, or re-enable global options in the script.
Related¶
- RAD20047 - a prompt was reached that couldn't be answered.
- RAD20010 - an interactive prompt failed or was canceled.
RAD20047: Prompt Reached Without A Usable Answer¶
The script reached a prompt that --reply couldn't answer. Unlike RAD20046, this one fires mid-run, because it depends on something rad could not know before starting.
Whichever way you got here, the script ran up to this point. Check what it already did before you re-run it: a retry starts from the top.
The Prompt Was Marked Unreachable¶
You passed --reply-na for this line, asserting the run wouldn't reach it, and it did:
rad deploy.rad --reply-na 20
That assertion is a promise, not a fallback, so rad stops rather than guessing an answer. Either the branch you expected to skip was taken, or the assertion was aimed at the wrong line. Replace it with a real --reply, or work out why the branch ran.
The Answers Ran Out¶
A prompt inside a loop consumed every answer supplied for its line and was reached again:
rad cleanup.rad --reply 12:yes --reply 12:yes # ran a third time
Rad does not reuse the last answer. One yes silently approving five hundred deletions is exactly the accident worth failing over. Supply as many answers as the loop has passes, or narrow what the loop iterates over.
A second shape lands here too, and rad cannot tell it from the first. A script that re-asks until it gets a value it accepts spends an answer on every attempt:
env = ""
while env not in ["prod", "staging"]:
env = input("Environment? ")
--reply 3:dev is rejected, the loop comes back around, and there is nothing left to give it. Adding flags cannot fix this one - --reply 3:dev --reply 3:dev fails in exactly the same place. Answer with a value the script takes.
The message names both readings because only you can tell which applies. Count the passes if the prompt sits in a loop; check the value if the script validates it.
The Answer Matched No Option¶
A pick or multipick over runtime data - a fetched list, a resource file, a computed set - got an answer that isn't in it:
fn fetch_servers():
return ["web-1", "web-2"]
server = pick(fetch_servers())
Rad can't check that up front, because the options don't exist until the script builds them. The error lists the real options, so the next run can name one exactly. Matching is exact by design: a near-miss fails rather than quietly acting on the wrong choice.
The Filter Already Chose¶
A pick given a filter that leaves one option takes it without asking. An answer naming anything else is a disagreement, and rad won't settle it by picking a side:
services = ["api", "worker", "scheduler"]
target = pick(services, "api", prefer_exact=true)
Here --reply 2:worker asks for one service and the script's own filter chose another. Usually the filter is the thing to change - it is normally built from an arg, so worker belongs there rather than in the answer.
An answer that names the option the filter settled on is consumed and the run carries on. Consuming it either way is what keeps a loop's answers in step with its passes, so a pick that settles on some passes and asks on others still lines up.
The Input Was Secret¶
input with secret set can't be answered from the command line at all - other processes on the machine can read your arguments. Where secret is written literally, RAD20046 says so before the script runs. Where it's computed, rad only finds out on reaching the prompt:
env = input("Environment")
needs_secret = env == "prod"
token = input("Token", secret=needs_secret)
Run it where a terminal is available, or use --reply-na if this run shouldn't reach it.
The Answer Broke A multipick's Rules¶
A multipick answer named an option twice, or gave more or fewer selections than the prompt allows. Where the bounds are literal, RAD20046 catches this up front; where they're computed, it surfaces here.
How to Fix¶
Rad prints the remaining prompts along with the failure, so a single re-run with the corrected --reply usually finishes the job.
Related¶
- RAD20046 - prompts need answers, raised before the script runs.
- RAD20010 - an interactive prompt failed or was canceled.
RAD20048: Shell Command Exited Non-Zero¶
A shell command finished with a non-zero exit code and nothing handled it.
Shell commands are critical by default in Rad: a command that fails raises an
error, and an error nobody handles ends the script. That's the opposite of
bash, where a failing command is ignored unless you opt into set -e.
Examples¶
$`false` // Error: Command exited with code 1
print("unreached")
The exit code carries through from whatever ran, so it tells you what happened:
$`grep nothing /etc/hosts` // Error: Command exited with code 1 - grep found no match
How to Fix¶
Decide whether the failure is fatal. If it is, you may want a better message than the default:
$`make build` catch:
print_err("Build failed - is the toolchain installed?")
exit(1)
If it isn't, say so. catch: pass continues without comment:
$`rm -f /tmp/scratch` catch:
pass
If the exit code is data rather than a failure - grep exiting 1 means "no
match", not "something went wrong" - capture it. Capturing the code doesn't stop
the error, so pair it with catch::
code, stdout = $`grep hello /etc/hosts` catch:
stdout = ""
if code == 0:
print("found: {stdout.trim()}")
else:
print("no match")
Two Failures That Aren't The Command's¶
Two cases report through this error even though the command never produced the code itself:
- You declined a
confirm. Declining reports exit code 1, so a declined command behaves exactly like one that ran and failed. Capture targets are set to empty output rather than left undefined. - The command couldn't be started at all. A binary that isn't installed reports 127, and one that isn't executable reports 126 - the same numbers a POSIX shell uses. A line on stderr names what went wrong.
$["definitely_not_installed"] catch:
print("not available, skipping")
See Also¶
rad docs guide/shell-commands- capture, modifiers, and the three command formsrad docs guide/error-handling-??, thecatchoperator, andcatch:blocks
RAD20049: Construct Not Available In The REPL¶
You typed an args block, a command block, or a file header at the REPL. All
three describe how a script is invoked from the command line, and a REPL
session was never invoked from anywhere.
An args block declares the flags and positionals rad parses out of argv
before the script runs. A command block declares a subcommand. A file header is
the docstring rad prints in that script's usage. None of them has a meaning at
a prompt, and the REPL says so rather than accepting them and doing nothing.
Examples¶
args:
name str
count int = 1
command greet:
name str
calls do_greet
fn do_greet():
print("hello {name}")
How to Fix¶
Put the script in a file, then load it:
:load ./greet.rad
:load runs the file's statements against your session, so functions and
variables it defines are yours to use afterwards. Its args block is skipped,
because there are still no command-line arguments to parse.
To work on the values an args block would have produced, assign them:
name = "world"
count = 3
To try the real argument parsing, run the script:
rad greet.rad --name world
See Also¶
rad docs guide/repl- what a session can and cannot dorad docs guide/args- declaring a script's command-line interface
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¶
count = 5
// Wrong: can't add int to string
x = "count: " + count
// Correct: use interpolation (preferred)
x = "count: {count}"
// Correct: explicit conversion
x = "count: " + str(count)
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, seerad docs migrations/v0.9.
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 rad docs migrations/v0.9 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).
RAD40016: Regex Pattern Without regex=true¶
You passed something that reads like a regex to split() or replace(), but
neither treats its pattern as a regex unless you ask.
line = "alice 30 admin"
// Warns: '\s+' is a regex, but split() is looking for that literal text
fields = split(line, "\s+")
// Correct
fields = split(line, "\s+", regex=true)
Why This Exists¶
Before v0.12, split() and replace() always treated their pattern as a regex.
That default was backwards - most calls want literal text - and it failed
silently in the dangerous direction: a . in a pattern quietly matched every
character.
The default is now literal. The catch is that scripts written for the old behavior keep running. They just stop matching:
print(split("a b c", "\s+")) // -> ["a b c"], not ["a", "b", "c"]
No error, no crash, only a wrong answer. This warning is the tap on the shoulder.
How to Fix It¶
If you meant a regex, add regex=true:
text = "order 66 shipped"
print(replace(text, "\d+", "N", regex=true)) // -> "order N shipped"
If the text really is literal, you can ignore the warning - the call already does what you want, and you no longer need to escape anything:
version = "1.2.3"
print(replace(version, ".", "-")) // -> "1-2-3", no escaping needed
Ignore It When¶
The heuristic looks for constructs that rarely appear in literal text: \d,
\s, a bracketed class, a group, a quantifier, alternation with branches on both
sides, or a leading ^ / trailing $. Real text does sometimes contain those -
"C++" and "a|b" are perfectly good literal separators. The warning is advice,
not a verdict.
See rad docs migrations/v0.12 for the full picture, and
rad check --from-logs all to sweep every script you've run recently.
RAD40017: Interpolating a Constant Literal¶
An interpolation contains a literal value, so it produces exactly the text
you already wrote. "{4}" and "4" are the same string.
This is almost always a literal brace that got read as an interpolation.
Example¶
// Wrong: {4} interpolates the number 4, so this is the regex \d4
pattern = "\d{4}"
// Correct: escape the brace
pattern = "\d\{4}"
// Correct: a raw string, where nothing interpolates
pattern = r"\d{4}"
Regex quantifiers are where this bites. \d{4} looks like "four digits"
and parses as "a digit, then the number 4". Wider quantifiers like
\d{2,3} are a syntax error instead, so the single-number form is the only
one that fails quietly.
How to Fix¶
- Escape the brace with
\{if you meant a literal{ - Use a raw string (
r"...") if the text is brace-heavy - regexes and JSON templates usually are - Write the value directly if you really did mean to interpolate a constant
Note that r"..." turns off all interpolation and escapes for that
string, so it is the wrong tool if you also need {name} to expand.
Why This Is a Warning¶
The rule is syntactic: it fires only when the interpolated expression is itself a literal. Interpolating a variable that happens to hold a constant is fine and stays quiet.
digits = 4
name = "ana"
print("{digits}") // no warning
print("{2 + 2}") // no warning
print("{name:<12}|") // no warning - 12 is a format width, not the value
RAD40018: Misleading Shell Capture Name¶
A shell capture target is named after one stream but receives another.
// Warns: 'output' isn't a reserved name, so the whole statement is
// positional - and position 0 is stdout, so 'code' gets the output.
code, output = $`git status --porcelain`
// Correct - all three names are reserved, so assignment goes by name
stdout, code = $`git status --porcelain`
The Two Rules¶
Shell commands produce three things: stdout, stderr, and an exit code. How your variables receive them depends on what you called them.
Named - when every target is spelled exactly code, stdout, or
stderr, each one gets the stream it's named after and order doesn't matter:
stderr = $`cmd` // just stderr
code, stdout = $`cmd` // exit code and stdout
stderr, stdout, code = $`cmd` // all three, any order
Positional - the moment one target isn't a reserved name, the whole
statement switches to filling (stdout, stderr, code) in order:
out = $`cmd` // out = stdout
out, err = $`cmd` // out = stdout, err = stderr
out, err, c = $`cmd` // out = stdout, err = stderr, c = exit code
This warning fires where those two rules collide: you used a reserved name, but something else in the statement dropped you into positional mode, so the name means nothing and the slot decides.
How to Fix It¶
Either name every target so assignment goes by name:
// Before
code, output = $`cmd`
// After
code, stdout = $`cmd`
Or drop the reserved names and let position do the work:
// Before
code, output = $`cmd`
// After - now positional throughout, and reads as it behaves
output, _, code = $`cmd`
The first form is usually what you want. It's a rename, and it captures exactly the same streams.
Migrating From v0.11¶
Before v0.12, positional captures filled (code, stdout, stderr) - the exit
code first. They now fill (stdout, stderr, code).
Scripts written against the old order still parse and still run. They just bind different variables:
// v0.11: version = stdout. v0.12: version = stderr.
_, version = $`rad -v`
When the warning mentions v0.12, that's the case it found. The fix is usually to drop a slot, because what used to need two targets now needs one:
version = $`rad -v`
The exit code moved last because Rad already turns a non-zero exit into an
error - you reach for catch: rather than for the code - and because a single
capture binding the exit code was the least useful thing it could bind.
What This Warning Cannot Catch¶
It reads names, so a positional capture whose names give no signal is invisible to it:
a, b = $`cmd` // no warning possible
Sweep every script you've run recently with rad check --from-logs all, and see
rad docs migrations/v0.12 for the full picture.
RAD40019: Two Default Commands At One Level¶
Only one command per level runs when the user names none, so two default
markers among siblings leave nothing to decide between them.
command add:
tokens str
default
calls do_add
command edit:
tokens str
default // Error: 'add' is already the default here
calls do_edit
How to Fix¶
Keep the marker on whichever command should run when none is named, and drop the other:
command add:
tokens str
default
calls do_add
command edit:
tokens str
calls do_edit
Nesting scopes this: each namespace picks its own default independently, so
remote and image can each have one.
command remote:
command add:
name str
default
calls do_remote_add
command image:
command build:
tag str
default
calls do_image_build
See Also¶
rad docs guide/script-commands- commands, nesting and defaults
RAD40020: Command Does Nothing¶
A command either runs something or routes to sub-commands. This one does
neither: it has no calls line and no nested command blocks, so invoking it
could only ever do nothing.
command deploy:
env str // Error: nothing to run
How to Fix¶
Add a calls line naming the function to run:
command deploy:
env str
calls do_deploy
fn do_deploy():
print("Deploying to {env}...")
For a short body, an inline lambda avoids naming a function you only use once:
command deploy:
env str
calls fn():
print("Deploying to {env}...")
Or nest command blocks inside it, which makes it a namespace that routes to them instead of running itself:
command deploy:
timeout int = 30 // shared with every sub-command
command staging:
calls do_staging
command prod:
calls do_prod
See Also¶
rad docs guide/script-commands- commands, nesting and defaults
RAD40021: Namespace Has A Callback¶
A command containing sub-commands is a namespace: it groups them and shares its
args with them, and the sub-command the user names is what runs. So a calls
line on it would never fire.
command remote:
calls do_remote // Error: 'remote' routes to its sub-commands
command add:
name str
calls do_add
How to Fix¶
Drop the calls line. Args declared on the namespace still reach every
descendant, which is usually what the callback was reaching for:
command remote:
timeout int = 30 // inherited by add, and by anything nested deeper
command add:
name str
calls do_add
If you wanted tool remote on its own to do something, mark one of its
sub-commands default - that is the command it runs when none is named:
command remote:
command list:
default
calls do_list
command add:
name str
calls do_add
Why Rad Works This Way¶
Shape decides role, so there is no keyword to remember and no way for the
declaration and the behavior to disagree. A block with calls runs; a block
with commands inside routes.
See Also¶
rad docs guide/script-commands- commands, nesting and defaults
RAD40022: Default On A Namespace¶
default marks the command to run when the user names none. A namespace does
not run - it routes - so marking one leaves the question unanswered.
command remote:
default // Error: 'remote' contains sub-commands
command add:
name str
calls do_add
How to Fix¶
Mark the sub-command that should run:
command remote:
command add:
name str
default
calls do_add
command remove:
name str
calls do_remove
tool remote origin now runs do_add, and so does tool remote add origin.
The full form stays available, which is how you pass a value that happens to
match a sibling's name - tool remote add remove adds a remote called
"remove". tool remote -- remove does the same.
See Also¶
rad docs guide/script-commands- commands, nesting and defaults
RAD40023: Quoted Interpolation In A Shell Command¶
An interpolated value in a shell command is wrapped in quotes the script wrote itself. Rad already quotes interpolated values, so these quotes are no longer protecting anything - they end up inside the argument as literal characters.
message = "hello"
$`git commit -m "{message}"` // Error: the quotes land in the message
How to Fix¶
Delete the quotes. Rad quotes the value for you, whatever it contains:
message = "hello"
$`git commit -m {message}`
When the argument is literal text plus a value, build the string first and interpolate that. The quotes were holding the two halves together as one argument; a single interpolation does that on its own:
version = "1.2.3"
// Wrong - the quotes become part of the commit message
$`git commit -m "Bump to {version}"`
// Correct
message = "Bump to {version}"
$`git commit -m {message}`
Why Rad Works This Way¶
Hand-written quotes could never be correct for every value. Double quotes still
expand $HOME and $(whoami); single quotes cannot contain an apostrophe at
all. So a command that worked for hello would break, or worse silently
misbehave, on it's or 100% of $USERS.
Rad quotes each interpolated value at the point it knows what the value is, so the argument the program receives is exactly the value you had:
message = "it's fine"
$`git commit -m {message}`
// runs: git commit -m 'it'\''s fine'
The rule is that the text you write is shell - pipes, redirects, && and all -
and interpolations are data.
See Also¶
rad docs guide/shell-commands- the three command formsrad docs RAD20045- values that have no single-argument form
RAD40024: Constraint Does Not Fit The Arg's Type¶
An arg carries a constraint its type cannot be checked against - an enum or
regex on a number, or a range on a string.
args:
scores int[]
scores enum ["low", "high"] // Error: scores holds ints
How to Fix¶
Use the constraint that matches the type:
| Constraint | Works on |
|---|---|
enum |
str |
regex |
str |
range |
int, float |
len |
any list or variadic |
So the example above wants either a string arg:
args:
levels str[]
levels enum ["low", "high"]
or a numeric bound:
args:
scores int[]
scores range [0, 100]
A list or variadic takes the same constraints as the type it holds, because the
constraint describes each element. scores int[] is checked with range, the
same as a plain scores int - every value has to be in range, not the list as a
whole. To bound how many values arrive instead, use len.
Why Rad Works This Way¶
Rad used to accept these declarations and ignore them. A script could say
scores range [0, 100] on an int[], pass --scores 500, and get no
complaint - the constraint reached the parser and was dropped there. That is
worse than rejecting it: the script reads as though it validates its input, so
whoever wrote it stops checking by hand, and the values flow on unexamined.
A constraint that cannot be applied is a mistake in the script rather than a preference, so Rad reports it instead of guessing at what was meant.
See Also¶
rad docs guide/args- declaring args and their constraints
RAD40025: Shell Command Used Without a Result Accessor¶
A shell command was used where a value is wanted, but nothing said which
result. Add .stdout, .stderr, .code, or .ok.
if $`git status --porcelain`: // Error: which result?
print_err("Uncommitted changes!")
How to Fix¶
Pick the accessor that matches the question you're asking:
// "did it print anything?"
if $`git status --porcelain`.stdout.trim():
print_err("Uncommitted changes!")
// "did it succeed?"
if not $`which docker`.ok:
exit(1)
Which Accessor¶
| Accessor | Type | Fails on non-zero exit |
|---|---|---|
.stdout |
str |
yes |
.stderr |
str |
yes |
.code |
int |
no |
.ok |
bool |
no |
.stdout and .stderr fail because output a failed command never produced
isn't an answer. .code and .ok never fail - asking about the outcome is
handling it, which is what makes them the right choice for testing a command.
Capture follows the accessor, exactly as it follows target names in a named
assignment: reading .stdout still lets stderr through to the terminal.
Why There's No Default¶
A bare command is ambiguous in a way that bites. In shell, if cmd tests the
exit code, but if $(cmd) tests whether it printed anything - two different
questions that look nearly identical. Requiring the accessor means the reader
never has to guess which one you meant.
Running a Command, Not Reading One¶
If you only want to run the command, it's already a statement - no accessor needed:
$`make build`
stdout = $`make build`
See Also¶
rad docs guide/shell-commands- capture, modifiers, and the three command formsrad docs RAD40026- postfix that isn't an accessor
RAD40026: Postfix After a Shell Command Isn't a Result Accessor¶
$ takes only the command, so whatever follows reads the command's result.
The only things that can follow are .stdout, .stderr, .code, and .ok.
This fires on three shapes, and the first two used to mean something else.
An Index or a Call on the Command¶
cmds = ["echo a", "echo b"]
$cmds[1] // Error
$ used to swallow the whole expression, so this indexed cmds first and ran
whatever came back. Now $ takes cmds as the command, and [1] is postfix
with nothing to index. Wrap the command in parentheses to get the old meaning:
$(cmds[1])
That form has always parsed and always run, so this is a mechanical fix. The same applies to any computed command:
$(parts.join(" "))
A Method on the Command¶
$`echo hi`.upper() // Error
This one ran ECHO HI - the method transformed the command text before it ran,
not the output afterwards, and nothing reported it. Read a result first:
$`echo hi`.stdout.upper()
A Name That Isn't a Result¶
$`echo hi`.output // Error: did you mean 'stdout'?
There are four results and no others. See rad docs RAD40025 for what each one
gives you.
Why This Is an Error, Not a Warning¶
Every one of these shapes either did something other than what it reads like, or has no meaning at all. There's no version of them worth keeping working.
See Also¶
rad docs migrations/v0.12- the full change and its rationalerad docs RAD40025- using a command as a value with no accessorrad docs guide/shell-commands- the three command forms