a9script

Docs / CLI, MCP & files

Debugging — the trace, and holding the wheel

Two tools, both sandbox only, both meant for exactly the position you are in: something is wrong and nobody is watching the screen.

The trace: what actually left the platform

A run started with --debug keeps every outbound call — the request as sent and every attempt back, headers and bodies included:

a9script run sync-contact --input _input.json --debug --json
a9script history <run> --json
a9script inspect <run> 1 --json
  • history lists the calls: name, connection, status, attempts, verdict, duration. It never carries a body. Its nextActions points at the call worth opening — the first that did not answer, else the last one made.
  • inspect <run> <seq> is that call whole. The credential is NAMED, never valued: you can see that a bearer token was attached without seeing it.
  • A body over the trace’s ceiling is cut, and says so with a truncated mark. A cut body is not the far end’s last word — do not read the end of one as the end of the response.

Read the response before you change the script. Most integration failures are a far end explaining itself in a body nobody looked at: an id format, a required field, a scope the credential does not have. Your run’s error is your script’s view of the failure; the trace is the far end’s.

Two refusals worth recognising:

  • NOT_A_DEBUG_RUN — that run kept no trace. Re-run it with --debug.
  • TRACE_EXPIRED — traces are short-lived by design; that run is too old. Re-run it with --debug for a fresh one. It does NOT mean the run made no calls.

Debug is refused outside a sandbox (DEBUG_REQUIRES_SANDBOX) — a debug run keeps payloads, which is not something production traffic may do.

A run you start this way is filed in the script’s own log beside the runs the platform dispatched, and marked: run --json answers origin: "cli" (the text form says succeeded · cli), so a person reading the log can tell your runs from the automation’s. You do not send the mark — the platform derives it from your credential, and a body that names origin is refused. A run started from the editor’s Test button carries origin: "draft" instead; a run with no origin is one a trigger dispatched. The automation’s numbers count only those.

A run you did not start

The trace exists only for runs you started with --debug. The runs a trigger dispatched — last night’s, a webhook’s — are reached through the run list:

a9script runs --json
a9script runs --script sync-contact --status failed --json
a9script runs <run> --json
  • runs lists the environment’s runs newest first, a page at a time. Each row says how it started (origin) and what it still has of its lines (lines): hot while they are searchable, archived once they moved to the run’s own archive, none for a run that wrote nothing, gone once the archive was collected and the row is all that is left.
  • runs <run> is that run whole — its lines wherever retention has put them, its outcome, its error. Open a row before you re-run anything: the failure is usually already written down.
  • Page back with the cursor the nextActions hands you: --before <run> continues from the last row drawn, exactly. --before <instant> (an ISO 8601 time with a timezone) jumps to a day instead.
  • A run that answers HARNESS_SLOW did start: the platform stopped waiting for it, and the run goes on under the script’s name. Read it with runs --script <name> — the next: line says so — and never run it again on that account; that starts a second one.

Stepping: one external call at a time

--step runs a script that pauses after every call that leaves the platform — a configured HTTP call, a model prompt, a mail send — and waits for you. It implies --debug, because a pause you cannot look into is theatre.

a9script run place-order --input _input.json --step --json

The command exits 0 with the run PARKED: starting the session is what you asked for. From there:

commandwhat it does
a9script sessionsevery paused run in this environment
a9script position <run>the parked call, the statement, the variables in scope
a9script continue <run>let exactly one more external call happen
a9script continue <run> --to-endstop stepping; finish as an ordinary debug run
a9script abort <run>stop here — nothing further is sent

A session id IS the run’s id. Nothing is stored on your side, so a fresh shell picks up a session with sessions.

A run that is not stepped — one going on that should not, a loop calling a far end — ends with a9script stop <run>: what it already sent stays sent, and nothing further is. It needs a full-scope token; runs <run> reads it after.

What position tells you. Whether the parked call has already been made (its answer recorded) or is still waiting to fire; which statement the script is on; and the variables in scope with type-faithful previews — "", null, undefined and “declared but not yet assigned” are four different findings and they look different. Large values are summarised rather than dumped.

Use it for the destructive middle of a new write path. The first time a script creates, updates or deletes at a far end, step it: check the arguments at the pause, continue if they are right, abort if they are not — an aborted session sends nothing further, and the far end saw only the calls you allowed.

Once the path is proven, run it normally. Stepping is a debugging posture, not a deployment one — nothing dispatched by a trigger is ever stepped.

A session does not wait forever. Left untouched it expires, and the verbs then answer STEP_EXPIRED rather than pretending it is still there. Other refusals in the same family:

  • NOT_A_STEPPED_RUN — that run was not started with --step. In particular, a run waiting because the far end asked it to wait is not a stepped session, and continue will not cut that wait short.
  • RUN_NOT_PAUSED — the run is not paused; the message says what it is now (it may have finished, or another command may already have ended it).
  • STEP_REQUIRES_SANDBOX — same reason as --debug.
  • SCRIPT_DISABLED — the script was switched off while the session was paused. The run stays parked until it is switched on again; abort still ends it.

continue answers what actually HAPPENED, not what was asked: resumed (a step is running), waiting (the run is parked on a far end’s own wait — nothing changed), or executing (a step is running right now; ask again with position).

Which one to reach for

  • The run failed and you want to know why → --debug, then history and inspect.
  • A run you did not start failed → runs, then runs <run> for its lines.
  • The run is going to write something and you want a say in whether it does → --step.
  • You want the shape of one call and no script is involved → a9script probe (config functions).
Rendered from docs/skills/a9script-authoring/reference/debugging.md in the product's own repository, at build time. Found a problem on this page? Write to the address in the footer.