Attach to a running Python process

Kolo can start tracing a CPython process that is already running, without restarting it or having enabled Kolo in advance. This uses Python 3.14’s safe remote debugger interface.

Run kolo attach with the target process ID:

kolo attach 12345 --duration 10 --name "slow checkout"

Kolo activates inside the target process, loads that process’s Kolo config, and writes the trace to its normal .kolo store. After saving, the command prints the trace ID and the matching kolo cat command. Omit --duration to trace until you press Ctrl-C.

Duration versus timeout

--duration controls how long Kolo records. --timeout only controls how long the command waits for each attach, start, or save handshake. It does not stop a healthy recording early.

Option

Meaning

Example

--duration SECONDS

Record for this long, then save. Omit it to record until Ctrl-C.

--duration 30 records for 30 seconds.

--timeout SECONDS

Give each attach/start/save handshake this long to complete. Defaults to 10 seconds.

--timeout 60 tolerates a target that is slow to reach Python or slow to save.

For example, this records for 30 seconds and allows up to 60 seconds for each handshake around that recording:

kolo attach 12345 --duration 30 --timeout 60

The attaching command and target must:

  • run CPython 3.14 or newer, using the same Python major and minor version;

  • have Kolo installed in the target’s Python environment;

  • run on the same host, with a shared view of the temporary directory and the same network namespace; and

  • use Kolo’s default sys.monitoring backend. Remove use_monitoring = false from the target’s config before attaching.

The target’s current working directory and KOLO_PATH determine which .kolo/config.toml and trace store are used. Filters, plugins, trace points, value capture, auto-emit, and other capture settings therefore behave the same way as when Kolo is enabled at process startup.

Note

The OS applies its normal native-debugger permissions. Linux may require CAP_SYS_PTRACE or a less restrictive Yama policy, containers may need --cap-add=SYS_PTRACE, macOS commonly requires sudo, and Windows may require an Administrator terminal. See Python’s remote debugging permission guide.

The injected code runs when the target next reaches a safe Python execution point. A process blocked indefinitely in a system call or native extension may not attach until it resumes executing Python bytecode. Kolo reports a timeout instead of waiting forever when that does not happen. Remote debugging can also be disabled in the target with PYTHON_DISABLE_REMOTE_DEBUG, -X disable-remote-debug, or CPython’s --without-remote-debug build option.

Kolo keeps a private local control connection open while tracing. Python’s sys.remote_exec() only queues a script; it does not return a result or provide a way to stop that script later. The local connection lets the target confirm that tracing really started, receive the stop request, confirm that the trace was saved, and notice if the attaching command disappears. It is bound to this machine only and protected by a one-use random secret. Normal target shutdown also saves the active attached trace.

For prefork servers such as Gunicorn or Celery, attach to the worker PID that is handling the workload. Each worker is a separate Python process and needs its own attach session.

Useful debugging workflows

  • Attach without --duration, reproduce a bad request or background job, then press Ctrl-C immediately after it finishes. This keeps an incident trace tightly scoped without restarting the process.

  • Put high-volume framework or application paths in the target’s Kolo filters, then attach to a busy long-lived worker for a short sample. The attached trace uses those filters and the target’s value-capture settings.

  • Attach to one worker at a time when a problem is isolated to a particular process. The command prints the target’s Kolo store, so the resulting trace remains discoverable even when the controller was run from another directory.