Safety and errors

Every failure comes back with a stable code your agent can branch on. Risky steps need consent every time, and credentials are masked before anything reaches disk.

Try it autonom network start

How it works

A command prints one JSON document on stdout and exits 0. An expected failure prints one error object on stderr instead and exits 2. The object always has ok, error_code, error and hint.

Four streaming modes print one JSON object per line instead: flow run --events, logs follow, network requests follow and journal --follow. You opt in to each. tour --human prints Markdown for a person.

A command box branches into four outcomes: exit 0 with JSON on stdout, exit 2 with an error object on stderr, exit 1 when a test failed, and exit 130 when interrupted. Below, the exit 2 object with ok false, error_code, error and hint. A command box branches into four outcomes: exit 0 with JSON on stdout, exit 2 with an error object on stderr, exit 1 when a test failed, and exit 130 when interrupted. Below, the exit 2 object with ok false, error_code, error and hint.
Every expected failure has the same shape, so one branch handles them all.

Your agent branches on error_code, never on the text. Codes may be added, but a code never changes meaning and is never removed. Every code the docs show is listed below.

Exit 1 means a test failed: a flow step, or a screen that never settled. doctor exits 0 even when tools are missing, unless you pass --strict. Exit 130 means you interrupted the command.

Warnings are softer. They sit in a warnings list, each with a code, and the command still succeeds.

Consent is asked every time

Some steps change trust or traffic: starting the network proxy, attaching a device, installing a CA. Each needs its own flag on every run. On an interactive terminal, you also type a confirmation phrase.

A refusal happens before anything runs, so a refused step changed nothing. No setting, file or earlier approval stands in for consent.

What stays protected

  • Loopback only. The proxy listens on 127.0.0.1, with no flag to widen it. Mobile Canvas and report serve do too.
  • Masked before written. Sensitive headers and credential-like body fields are masked at capture. The saved files never held them.
  • Owner-only files. Only your user can read the network folder and flow files. Capture refuses a folder anyone can write to.
  • Your Mac's settings stay put. Autonom never changes the Mac's own network settings.
  • Teardown restores. network detach puts back the exact proxy setting it found.

Examples

Every output here comes from Autonom's test device, so you can reproduce it without an emulator. The commands are in Reproduce these outputs, after the examples.

Branch on the code when the target is ambiguous

Goal: see what your agent gets when two emulators are ready and no target is named.

  1. Run any command that needs a target.

    Autonom refuses to guess between the two.

    autonom ui tree

You should see one error object on stderr, with exit code 2.

{"ok": false, "error_code": "ambiguous_target", "error": "2 ready targets; pass --target (or --platform)", "hint": "Candidates: android:emulator-5554, android:emulator-5556", "candidates": [{"platform": "android", "target_id": "emulator-5554"}, {"platform": "android", "target_id": "emulator-5556"}]}

What to look at. ambiguous_target is the branch. candidates gives your agent the exact values to pass to --target.

Goal: confirm that a step without its consent flag changes nothing, and that the journal still shows it.

  1. Start a session.

    The journal starts with it.

    autonom session start --serial emulator-5554 --app-id com.example.app
  2. Start the proxy without its flag.

    The step is refused before anything runs, with exit code 2.

    autonom network start
  3. Read the journal.

    Every command is recorded, refusals included.

    autonom journal

You should see the refusal in the journal as ok: false with its code.

{ "ok": true, "count": 2, … { "seq": 2, … "verb": "network start", … "ok": false, "origin": "agent", "error_code": "consent_required"

What to look at. The refused step is on the record, even if your agent never mentions it. The refusal itself, consent_required, names the flag to add. In the test run, the session's network folder stayed empty.

Show the refusal
{"ok": false, "error_code": "consent_required", "error": "refused without explicit consent — mitm_proxy on 127.0.0.1:auto: start a man-in-the-middle proxy that decrypts and records this session's HTTP(S) traffic to disk", "hint": "Re-run with the required flag(s): --i-understand-mitm. On an interactive terminal you will also be asked to type the confirmation phrase.", "operation": "mitm_proxy", "target": "127.0.0.1:auto"}
Show full output
{ "ok": true, "count": 2, "total_matched": 2, "truncated": false, "entries": [ { "seq": 1, "ts": "…", "kind": "action", "verb": "session start", "argv": [ "--adb", "…/tests/fakes/fake_adb.py", "session", "start", "--serial", "emulator-5554", "--app-id", "com.example.app" ], "ok": true, "origin": "agent" }, { "seq": 2, "ts": "…", "kind": "action", "verb": "network start", "argv": [ "--adb", "…/tests/fakes/fake_adb.py", "network", "start" ], "ok": false, "origin": "agent", "error_code": "consent_required" } ], "journal": "…/.autonom/sessions/s_…/journal.ndjson" }
A flowchart: a privileged step checks for its flag, then on a terminal for the typed phrase. A missing flag leads to consent_required, a wrong phrase to consent_declined. Only after both does the change run and the grant get logged. A flowchart: a privileged step checks for its flag, then on a terminal for the typed phrase. A missing flag leads to consent_required, a wrong phrase to consent_declined. Only after both does the change run and the grant get logged.
A step runs only after its flag, and on a terminal its phrase.

Read a validation error by its position

Goal: find the exact line that makes a flow file invalid.

  1. Check the flow file.

    Line 9 asks for a match mode that does not exist.

    autonom flow check flows/smoke.yaml

You should see the file, line and column, and a hint with the valid match modes.

{"ok": false, "error_code": "flow_selector_invalid", "error": "…/flows/smoke.yaml:9:14: unknown match mode 'exactly'", "hint": "Did you mean 'exact'? Match modes: caseInsensitiveExact, contains, exact, regex.", "file": "…/flows/smoke.yaml", "line": 9, "column": 14}

What to look at. The error object adds file, line and column to the usual keys. The hint lists every valid mode, so the fix is one word: exact.

Show the flow file
1 schema: autonom.dev/flow/v1 2 appId: com.example.app 3 name: Smoke 4 --- 5 - launchApp 6 - tapOn: 7 selector: 8 text: Add to cart 9 match: exactly 10 - assertVisible: 11 selector: 12 id: cart_badge 13 timeoutMs: 3000

Fail cleanly on a machine with no devices

Goal: see what a command says on a machine with no device tools at all.

  1. Take a screenshot.

    There is no emulator, no Simulator and no adb.

    autonom screenshot

You should see one error code and a hint, and no traceback.

{"ok": false, "error_code": "no_target", "error": "no ready target found", "hint": "Start an emulator or boot a simulator, then run 'autonom devices'."}

What to look at. The hint names the next command. Your agent can follow it instead of guessing why nothing happened.

Reproduce these outputs

From the root of an Autonom d0f7211 checkout, in bash or zsh. Autonom's test device stands in for two emulators; --platform android keeps a booted Simulator out of the list. The last command runs on an empty PATH, like a machine with no device tools. Session ids and times differ on every run, and paths are shortened to … on the page.

S=$PWD W=/tmp/autonom-safety rm -rf $W && mkdir -p $W/flows export AUTONOM_HOME=$W/.autonom autonom() { python3 $S/scripts/autonom.py --adb $S/tests/fakes/fake_adb.py "$@"; } echo '{"devices": [["emulator-5554", "device", "product:sdk_gphone64_arm64"], ["emulator-5556", "device", "product:sdk_gphone64_arm64"]]}' > $W/two.json AUTONOM_FAKE_STATE=$W/two.json autonom --platform android ui tree autonom session start --serial emulator-5554 --app-id com.example.app autonom network start autonom journal ls -A $AUTONOM_HOME/sessions/*/network cd $W printf '%s\n' 'schema: autonom.dev/flow/v1' 'appId: com.example.app' 'name: Smoke' '---' '- launchApp' '- tapOn:' ' selector:' ' text: Add to cart' ' match: exactly' '- assertVisible:' ' selector:' ' id: cart_badge' ' timeoutMs: 3000' > flows/smoke.yaml autonom flow check flows/smoke.yaml env -i PATH= AUTONOM_HOME=$W/bare "$(command -v python3)" $S/scripts/autonom.py screenshot

Good to know

  • Branch on error_code, never on the text. The message may change. The code does not.
  • Exit 0 does not prove a tap changed anything. Compare before and after screenshots or trees.
  • verified means read back. verified: false with ok: true is a success whose effect is unconfirmed.
  • Consent is never remembered. An approval earlier in the same session does not carry to the next command.
  • Credentials are masked before they are written. Not just before they are shown, so the files on disk never held them.
  • A mocked screenshot is flagged. It carries screenshot_shows_mocked_data. Never present it as real backend behavior.
  • Pinning is not bypassed. Use a debug build with certificate pinning relaxed, and say plainly when traffic cannot be read.
  • Session files are sensitive. They live outside any repository, so no .gitignore protects a copy. Delete them when the work is done.

Reference

The commands that gate, record and undo what Autonom changes.

CommandWhat it does
autonom doctor --strictExits 1, with ok: false and strict_failures, when the machine is not ready.
autonom network start --i-understand-mitmStarts the capture proxy. The flag is needed every time.
autonom network attach --i-understand-mitmRoutes the device through the proxy. Installing a CA also needs --install-ca.
autonom network detachPuts the device's proxy setting back as it was.
autonom journalThe session's timeline, failures and refusals included.
autonom processesEvery process Autonom started on this machine.
autonom cleanupStops leftover processes, each only after checking it still runs the recorded command.
autonom session stopStops the proxy, the streams and the session's processes.
xcrun simctl keychain <udid> resetRemoves a CA from a Simulator. network detach leaves it there on purpose.

Error codes

Each code below appears in these docs. Your agent reads it from error_code in the error object.

ambiguous_selector

When: A selector matched more than one node, and the command acts on one.

Do: List the matches with --all, then pass --index or a tighter selector.

Read and drive the screen

ambiguous_target

When: More than one target is ready, and none was named.

Do: Pass one of the candidates with --target, or narrow with --platform.

Sessions and targets

app_not_debuggable

When: Android refused run-as, so the app's files cannot be read.

Do: Install a debug build of the app.

Device state

app_not_installed

When: The app is not installed on the target, or no app id was given.

Do: Check the package id, or start the session with --app-id.

Sessions and targets

backend_failed

When: A device tool that Autonom calls, such as adb, failed.

Do: Read error. A CA install fails this way on a Play Store image; use another.

Network

When: On a terminal, the typed phrase did not match. Nothing changed.

Do: Run the command again and type the phrase exactly.

Network

When: A step that changes trust or traffic ran without its flag. Nothing ran.

Do: Add the flag the hint names, such as --i-understand-mitm.

Network

coordinate_space_mismatch

When: A tap point lies outside the target's screen.

Do: Take the point from the tree's bounds, in points on iOS, or tap by selector instead.

Read and drive the screen

element_offscreen

When: The matched node is not on screen, so nothing was tapped.

Do: Scroll it into view, then select it again.

Read and drive the screen

flow_assertion_timeout

When: An asserted element never appeared within the step's timeoutMs.

Do: Replay up to that step, read the tree, and check the label the screen really shows.

Flows

flow_file_not_found

When: A flow file does not exist at the path given.

Do: Check the path, then run flow check on it.

Flows

flow_selector_invalid

When: A selector in a flow file is malformed, such as an unknown match mode.

Do: Fix the line the error names; the hint lists the valid values.

Flows

invalid_simulator_action

When: A simulator control got an action it does not have.

Do: Pick one from valid_actions in the error.

Device state

ios_hid_framework_missing

When: idb's companion cannot load the Simulator's input framework, as on Xcode 27.

Do: Upgrade idb-companion, or install AXe.

Install

no_matching_node

When: No node on screen matched the selector.

Do: Read the tree and match what it says. The label may be in --desc.

Read and drive the screen

no_target

When: No ready target was found, or a command needed an app id or target it did not get.

Do: Start an emulator or boot a Simulator, then run autonom devices.

Sessions and targets

physical_device_attach_unsupported

When: Network capture or a CA install was asked of a physical Android device.

Do: Use an emulator. A physical device cannot reach the loopback proxy.

Network

selector_index_out_of_range

When: --index is past the number of matches.

Do: Count the matches with --all, then fix the index.

Read and drive the screen

session_already_active

When: A session already owns the target.

Do: Stop it first with autonom session stop.

Sessions and targets

unsupported_capability

When: The target cannot do what was asked, such as a capability a flow requires.

Do: Check what this machine and target support with autonom doctor.

Device state

unsupported_on_platform

When: The command does not exist on this platform, such as pinch, rotate or shake.

Do: Run it on the platform that has it, or do the same thing another way.

Read and drive the screen

usage_error

When: The command line is wrong: an unknown command or flag, or flags that clash.

Do: Read hint; it holds the usage line.

How a failure looks

Warning codes

A warning rides in the warnings list of a successful answer. The command still did its work.

companion_left_running

When: Stop found an idb companion the session did not start, and left it running.

Do: Stop it with kill if nothing else uses the Simulator.

Sessions and targets

cpu_stale

When: A CPU figure came from an old sampling window.

Do: Measure a series under a fixed flow before you make a claim.

Performance and memory

flag_ignored_on_platform

When: A flag does nothing on this platform, such as --activity on iOS.

Do: Drop the flag. The hint says why it has no effect.

Sessions and targets

label_is_in_desc

When: --text matched nothing, but the same value matches the description.

Do: Retry with --desc, as the hint shows.

Read and drive the screen

log_filter_degraded

When: Logs could not be narrowed to the app by its uid, so a weaker filter ran.

Do: Launch the app first, or use a device on API 31 or later.

Evidence

no_focused_field

When: No field had keyboard focus, so the typed text may be lost.

Do: Tap the field first, then type, and confirm with ui find.

Read and drive the screen

no_frames

When: gfxinfo counted no frames, as for a Flutter app.

Do: For Flutter, summarize frame timings with flutter-summary.

Performance and memory

override_path_missing

When: An AUTONOM_* override points at a binary that does not exist.

Do: Unset it, or point it at a binary that exists.

Install

persistent_mocks_active

When: Mock rules from earlier are still switched on, so matching responses will be faked.

Do: Review them, then run autonom network mock disable --all.

Network

screenshot_shows_mocked_data

When: A screenshot was taken while a mock rule was active.

Do: Never present it as real backend behavior.

Evidence

signal_unstable

When: Pinned signal bars cannot be read back, or the pin was not sent again.

Do: If the pin was not sent, pin the status bar again before you capture.

Evidence

sparse_accessibility_tree

When: On iOS, fewer than three meaningful nodes were found.

Do: Add accessibility labels in the app, or fall back to a screenshot.

Read and drive the screen

url_opened_chooser

When: A URL opened the system chooser, not an app. The command still exited 0.

Do: Verify the App Link, or pick the app in the chooser.

Device state

Android and iOS differences

The two platforms differ in what capture can prove and what teardown undoes.

CapabilityAndroidiOS Simulator
Network captureYes, through the emulator's host addressPartly: its traffic looks like your Mac's
A physical deviceRefused, with physical_device_attach_unsupportedNot a target: Autonom drives the Simulator only
Installing a CAYes on an emulator; a Play Store image fails with backend_failedStays after detach, until you reset the keychain
Pinch, rotate, shakeRefused, with unsupported_on_platformRefused the same way

Next steps

You can read every failure by its code and know what consent protects.