Skip to main content
A debug run keeps the failed job and its microVM available for inspection. Attach with preloop debug, run shell commands in the guest, synchronize a fix, and retry the failed step or an earlier step. Use --preserve-on-failure instead when you need a post-mortem shell without pausing the worker for a verdict. Debug session lifecycle: pause on failure, attach, then retry, continue, or abort

Choose how a failure is held

--debug and --preserve-on-failure are alternative failure modes. A plain run on the default backend keeps its terminal behavior for compatibility; explicit flags make scripts and agents unambiguous.

Attach to a paused job

Run preloop debug without a reference when exactly one session is paused:
With multiple sessions, the command lists each session and prints an attach command. A session ID, run ID, or job name can select one directly:
--json prints the paused session projection for scripts. The failure banner includes the step command, exit code, workflow position, working directory, structured diagnostics, and a short log excerpt. The job and microVM remain paused while you inspect them. Anything entered without a leading colon runs as a non-login sh -c command in the failed job’s workspace:
Keep credentials out of commands and output. DAP variables and debug logs use the same masking rules as normal job output.

Control the session

At the preloop debug prompt, colon-prefixed commands control the session. Other input goes to the guest shell. The CLI exposes the same non-interactive decisions:
--revert accepts none, untracked, or all. A retry restores runner-managed state such as step outcomes and file-command contexts. It does not undo external API calls, processes outside the workspace, or other external side effects. A step accepts at most 25 attempts.

Preserve a VM for a shell

Use this path when a completed failure is easier to diagnose from a normal shell than from a paused step:
With exactly one preserved VM, preloop shell finds it automatically. If several VMs are held, use the name printed by the error:
The shell command reads the local preserved-VM records, so run it on the host that owns the VM. Exiting the shell releases the active attachment but keeps the VM discoverable for reattach; the hold is cleaned up after 10 minutes of idleness. A shell session is post-mortem access, not a live step verdict.

DAP debugging

The DAP flow is useful when an agent or editor needs structured scopes and stack frames. Enable it on the run submission with the native API:
The runner waits for a DAP client to connect and send configurationDone. The default connection window is 15 minutes; ACTIONS_RUNNER_DAP_CONNECTION_TIMEOUT changes that value in minutes. The built-in client supports:
The adapter exposes github, env, runner, job, steps, and secrets scopes. Secret values are masked. next, stepIn, and stepOut resume like continue; execution stops at job step boundaries rather than inside a shell command. The client can inspect the final state after job completion before the DAP transport closes. preloop dap RUN_ID accepts --url URL and --token TOKEN. The endpoint is standard DAP over a WebSocket route, so a DAP-capable editor such as VS Code can use the same run endpoint with a WebSocket-capable adapter. The recorded omp workflow uses the small dapctl reference client; the same sequence is shown below. An agent attaches to a failing run, inspects DAP context, and reruns it The recording finds github.event.inputs.target in the github scope, identifies the wrong deployment input, and submits the corrected payload. It is a demonstration of the DAP inspection loop; use your own workflow and credentials for a real run.
The complete recording shows the agent’s whole session, from attaching to the paused run through the corrected rerun.The same recording is available as an animated GIF and as asciinema casts (docs/demo/dap/demo.cast, docs/demo/dap/demo-highlight.cast) in the repository.

Session limits and clean verification

A paused session has one controller at a time. The server records operations with an idempotency key and a session version, so a reconnecting controller does not execute a retry twice. A worker that stops polling for 90 seconds is no longer live, and a session with more than four hours of accumulated pause credit returns to ordinary timeout handling. These limits protect the job’s normal timeout and the runner pool. A retry is evidence from the warm, already-mutated VM. It does not prove that a fresh VM can reproduce the result. After fixing the checkout, submit a new run for clean verification. A paused job also continues to use the workflow graph compiled for that run; edit workflow structure in the checkout and submit a new run rather than expecting the paused job to adopt it. For command details, see the CLI reference and job lifecycle.