Guide 08 of 9
Troubleshoot a run
The first thing to check for each common outcome, from an unknown program to a missing speed-up.
First checks
| Symptom | Check |
|---|---|
ImportError or AttributeError for a documented name |
An older Cuprum may be installed: pip and uv skip a pre-release unless it is named, so pin cuprum==0.2.0b2. |
UnknownProgramError |
Register the exact program in the builder’s catalogue, or in the active scoped(catalogue=...), which sh.make now resolves. |
ForbiddenProgramError |
Inspect the active scope and its parent’s allowlist; a builder can be made from a catalogue the scope does not allow. |
TypeError while building a command |
The message names the problem: an unsupported value type, None, or a reserved execution option such as cwd=, which belongs on run_sync. |
FileNotFoundError |
Confirm the executable’s path, or the PATH the child inherits; a replacement environment without PATH cannot resolve a bare name. |
ExecutableResolutionError |
An executable-binding resolver failed or returned the wrong type; its own exception is chained as __cause__. |
TimeoutExpired |
Set a deadline that fits the work, and look for slow or blocked work in the child. |
ValueError naming a redirected stream |
Capture or echo with a redirected stream, one path for both streams, or a redirect with lines() or start() is contradictory; see the rejected combinations under Control output. |
StdinSourceError |
The stdin producer failed or yielded something that is neither str nor bytes; its own exception is chained as __cause__. |
RedactionError |
A redaction rule failed and nothing was exported unredacted. Check the rule the message names. |
SignalAfterExitError |
The child has already exited; treat the recorded outcome as the command’s ending. |
UnsupportedSignalError |
Use terminate() or kill(), which use the platform’s own signals. |
ValueError from run_bytes() |
A byte-exact run refuses on_line; observe lines with sh.observe() instead, or run in text mode. |
ValueError at spawn with OWN_GROUP |
Windows has no POSIX process groups; keep ProcessGroupPolicy.INHERIT there. |
A non-zero exit_code |
Read the captured stderr and the tool’s documented exit codes. |
No visible output |
Capture is silent by default: pass RunOutputOptions(echo=True), or read result.stdout. |
No heartbeat |
Supply a positive idle_after; any output resets its clock. |
Descendants outlive a timeout |
Opt in to process-group ownership on POSIX, so teardown signals the group the child leads. |
No native speed-up |
The extension may be missing, or this operation may use the Python path. |
Check the native extension
is_rust_available reports whether the native backend
can be used. It returns False on a pure-Python install
rather than raising, while other import failures still surface, so a
broken installation stays visible.
import cuprum
assert isinstance(cuprum.is_rust_available(), bool)
Run 2026-10-19 against Cuprum 0.2.0b2 · Python 3.12 · Linux
Behaviour is the same with or without the extension. The Rust extension page covers backend selection and why a hop falls back to Python.