Troubleshooting
Each entry is a fault you can see, what the runtime did, and what to change. If you hit a boundary of the design rather than a fault, see known limitations.
Something is blocked
Task ends with a budget denial
- Symptom: the task terminates with a reason like
max_iterations=5 exceededormax_tool_calls=3 reached. - Cause:
BudgetGuarddenied the next action because an axis was crossed:max_iterations,max_tool_calls,max_cost_usd,max_spawned_subtasksormax_subtask_depth. - Fix: read the task's event log to see which axis fired, then raise the cap with a
BudgetSpecinOptions.budget, or narrow the task.max_cost_usdonly fires for models the catalog prices (see below).
Tool call denied
- Symptom: a
ToolCallDeniedevent withtool 'X' denied by policy,tool 'X' not in allowlist, ortool 'X' risk_level 'high' exceeds max 'medium'. - Cause:
PermissionGuardchecksdenied_tools(fromOptions.disallowed_tools) and theallowed_toolsallowlist first, then the tool'srisk_levelagainst the agent's ceiling. - Fix: add the tool to
Options.allowed_tools(it replaces the default set) or drop it fromdisallowed_tools. For a risk-level denial, give the tool its own agent instead of raising the ceiling for everything.
Tool call waits for approval
- Symptom: the task suspends with
tool 'X' requires human approval(ortool 'X' call requires human approvalfor a per-call gate). - Cause:
permission_mode.defaultgates every tool whoserisk_levelis notlow;acceptEditsdoes the same but exemptsEditandWrite;bypassPermissionsgates nothing. ABashcommand outside the shell allowlist is gated per call in any mode. - Fix: resolve it with
Client.approve/Client.deny, or rule on it in code withOptions.can_use_tool. Changepermission_modeif the whole class should run unattended.
Write refused: path outside the workspace
- Symptom:
EditorWriteerrors that the path resolves outside the workspace or the writable allow-list. - Cause: writes are fenced to the task workspace. The path is canonicalised first (
..and symlinks collapsed), and containment is per path component, so/srv/app-oldis not inside/srv/app. Reads are not fenced. - Fix: write inside the workspace, or grant the directory through
HostConfig.write_roots(atask_id -> directoriesresolver, consulted per call, so a grant made while the task is paused applies when it resumes).
Something never happens
Suspended task never wakes
- Symptom: the task stays
suspendedalthough its condition looks satisfied. - Cause: one of three: the wake event has not happened yet (a timer's
fire_atis in the future, a subtask is not terminal); it happened but does not match the task'sWakeCondition; or no worker is draining the queue. - Fix: check the timer's
fire_ator the child's status, read the task's raw event stream, and make sure a worker is running — nothing starts one for you (Deploy).
Configuration is rejected
"unknown plugin activation"
- Symptom:
compile_optionsraisesValueError: unknown plugin activation 'x' on .... - Cause: a name in
Options.pluginsorAgentDefinition.pluginsis neither a built-in activation nor in thePluginSethanded toClient. Typos fail loudly so a capability cannot silently turn off. - Fix: fix the spelling, or
load_plugins(...)and pass the result asClient(options, plugins=...). The error lists both valid sets.
Model or provider rejected before the turn
- Symptom:
ModelSelectorError(model_selector_rejected) orProviderSelectorError(provider_selector_rejected); no task is written. - Cause: the model is outside
principal.allowed_models∩ the deployment allowlist, or the(provider, model)pair names an unconfigured provider or a model it does not declare. - Fix: pick from the
allowed/availablelist on the error, or widen the host's allowlist and provider registry.
401 or another authentication error
- Symptom: turns fail with an auth or permission error from the LLM endpoint.
- Cause: the API key is missing, expired, or lacks access to the model.
- Fix: check the key passed to the provider, or the environment variable it reads. Behind a proxy set
HTTPS_PROXY; the adapters usehttpx, which honours it.
"Model not found" from the endpoint
- Symptom: the provider returns an unknown-model error.
- Cause:
modelis not an id that endpoint serves. - Fix: pass an exact id the endpoint serves (for example
claude-sonnet-5; seecatalog_models()for the ids Noeta knows) and check your key's access tier.
Something degrades silently
An uncatalogued model warns and costs $0.00
- Symptom: a one-time
noetawarning naming the model; compaction assumes a 128K window even if the model is larger;GovernanceState.coststays zero. - Cause: compaction, the output cap and pricing all come from the model catalog. An unknown model gets a 128,000-token window, a 16,384-token output cap and a price of
0.0, each announced once in the log. - Fix: register a
ModelSpecrow —HostConfig(extra_models={...})orregister_modelsfromnoeta.sdk.providers. See Connect a model.
Workers misbehave
Step abandoned on shutdown
- Symptom: after SIGTERM the log shows
shutdown_abandonedandloop.abandonedisTrue. - Cause: the in-flight step outlived
shutdown_grace_s(30 s forWorkerLoop, 10 s forClient.start_workers). - Fix: exit the process — Python cannot stop the abandoned thread, and reusing the loop in-process is unsupported. The lease then expires and
requeue_stale()reclaims the task on the next start. To avoid it, raiseshutdown_grace_s, or setNoneto wait forever (a stuck step then needskill -KILL <pid>).
A long step fails with InvalidLease
- Symptom: a long-running step fails on its next event-log write; the worker emitted
heartbeat_invalid_lease. - Cause: heartbeat extensions are capped at the dispatcher's
heartbeat_max(360), so one step holds a lease for at mostheartbeat_interval × heartbeat_max. - Fix: treat it as a signal to inspect the task, not a recovery path. If the step is legitimately that slow, raise
heartbeat_intervalorheartbeat_max; otherwise find what hangs.