preloop-runner contains the two responsibilities that the official runner separates into Runner.Listener and Runner.Worker. The listener owns the long-lived connection and starts one worker child process for each job. The worker owns the job lease, step execution, and result reporting.
Listener process
Whenpreloop-runner run starts, the listener:
- Loads
.runner,.credentials, and.credentials_rsaparamsfrom the runner root. - Uses the stored RSA key to request an OAuth access token. It refreshes the token before its server-reported expiry.
- Creates a runner session and long-polls for messages. The default
brokerpath uses the current job service.--via azdoselects the older distributed-task session and message path. - Acknowledges a job request, then acquires the complete payload from its run-service URL when one is provided. Local messages can already contain the full payload. It starts a worker child for the job.
- Keeps polling while the worker runs. Polls advertise
Onlinewhen idle andBusywhile a worker is active, so cancellation messages can arrive during a job. - Deletes the session when it stops.
Worker process
The listener starts the hidden worker command from the same binary. It sends newline-delimited JSON over the worker’s standard input:jobcontains the acquired job payload.cancelasks the worker to stop with a timeout.shutdownasks it to finish the current job and exit.
- Renews the job lease before touching the workspace. If the first renewal cannot establish a valid lease, it runs no steps and reports the job as abandoned or canceled.
- Creates the workspace below the runner root, prepares remote actions, and builds the job environment and GitHub context.
- Runs the workflow steps in order. Shell
run:steps execute as child processes;uses:steps dispatch according to their action manifest:- Node actions use the bundled Node 20 or Node 24 runtime.
- Composite actions expand into nested steps.
- Docker actions and job containers use the
dockerCLI.
- Renews the job lease in the background while steps run. Losing the lease stops normal execution and reports the failure.
Logs, timelines, and completion
The worker reports through the service advertised in the job payload:- Step transitions go through the workflow-step results service on the broker path, or timeline records on the AzDO path.
- Console output is sent to a live results WebSocket on a best-effort basis.
- Step and job logs are uploaded through signed blob URLs. These uploads are the durable log record even when live streaming is unavailable.
- The final
completejobrequest carries the job conclusion, step results, annotations, and outputs. The worker logs whether the server acknowledged that request.
Cancellation and shutdown
A serverJobCancellation message is matched to the active job ID and forwarded once to the worker. The runner uses the supplied timeout, defaults an absent timeout to 300 seconds, and clamps shorter values to 60 seconds. It hard-kills the worker 15 seconds before the effective timeout; the kill covers the worker’s process tree so child shell and action processes do not remain behind.
Ctrl-C, SIGTERM, and a service RunnerShutdown message use a separate 60-second graceful shutdown window. The listener asks the worker to shut down, waits for completion, then kills it if it does not exit in time and force-completes it as canceled. A worker-reported failure is force-completed as failed. It removes the broker session before exiting.
--once and a runner configured with --ephemeral return after the first job completes. The listener still performs the normal completion and session cleanup path before returning.