kosa8 docs

Guides / Python / Errors and troubleshooting

Python errors and troubleshooting

Every exception the kosa8 package raises, with a real message from kosa8 0.9.36 and what to do about it, then a table of problems you can meet and how to fix them.

How this page was tested

The messages on this page were produced by kosa8 0.1.0 and 0.1.1 from PyPI on CPython 3.13.13 against a kosa8 daemon 0.9.36 on an Apple silicon Mac (macOS 26.0.1), by scripts that provoke each error on purpose. The ones that could not be provoked on that machine without changing its org policy or locking its screen, namely Kosa8PolicyError, Kosa8ApprovalRequiredError, Kosa8UnsupportedError and the failed rewind, are described from the SDK's source (kosa8/errors.py, kosa8/client.py) and say so. So is Kosa8ProtocolError, which needs a daemon that sends a broken stream. Where 0.1.1 changed what is raised, the page shows 0.1.1 and says what 0.1.0 did.

What this is, in plain words

When something goes wrong, the SDK raises an exception: Python's way of stopping and saying what happened. kosa8's exceptions are sorted into kinds, so your program can react to each kind differently: "the daemon is not running" calls for a different response than "that sandbox does not exist" or "the company policy forbids this".

Most messages come straight from the kosa8 daemon and end with advice in brackets: what to do next. The SDK keeps the problem and the advice in separate attributes, so a program can show the advice to a person, or hand it to an AI agent as the reason its step failed.

One thing is not an exception: a command in the sandbox that fails. If make test exits with code 2, run or exec returns an ExecResult with exit_code == 2. An exception means kosa8 itself could not do what you asked.

The hierarchy

Exception
└─ Kosa8Error                      anything this SDK raises; also client-side checks
   ├─ Kosa8ConnectionError         the daemon could not be reached
   ├─ Kosa8TimeoutError            a socket timeout passed
   ├─ Kosa8StreamError             run, exec or attach ended with an error frame (output kept)
   ├─ Kosa8ProtocolError           a streamed frame was not valid NDJSON (0.1.1)
   ├─ Kosa8CheckpointError         checkpoints: none to rewind to, bad name, lock timeout, failed rewind
   └─ Kosa8ApiError                the daemon answered with an HTTP error (any status, e.g. 500)
      ├─ Kosa8BadRequestError      400
      ├─ Kosa8AuthError            401
      ├─ Kosa8PolicyError          403
      │  └─ Kosa8ApprovalRequiredError   403 with a signed-approval refusal
      ├─ Kosa8NotFoundError        404
      ├─ Kosa8ConflictError        409
      └─ Kosa8UnsupportedError     501

All of them are importable from kosa8. Catch the specific class first and Kosa8Error last; catch Kosa8ApprovalRequiredError before Kosa8PolicyError, since it is a subclass. Plain Kosa8Error (not a subclass) is raised for checks the SDK makes before sending anything, and for a second write_stdin (0.1.1). On 0.1.0 a failed rewind was also a plain Kosa8Error; from 0.1.1 it is a Kosa8CheckpointError, which existing except Kosa8Error handlers still catch.

Attributes

AttributeSet onMeaning
message, str(e)allFor a daemon error: "<error> (<remediation>)", as the CLI prints it.
statusKosa8ApiError and subclassesThe HTTP status. None elsewhere.
daemon_messagedaemon errors, stream errorsThe daemon's error text, verbatim (or its message, or a text body).
remediationdaemon errors, when the daemon gave oneWhat to do next. None for many 400s and 401s.
method, pathdaemon, connection and timeout errorsThe request, such as DELETE /kosa8/v1/sandboxes/doc-py-nope.
bodydaemon errorsThe decoded response body, a dict for JSON.
__cause__connection and timeout errors, failed rewindsThe underlying exception (raise … from), such as FileNotFoundError(2, …).
stdout, stderrKosa8StreamErrorOutput produced before the error, as str.
approvalKosa8ApprovalRequiredErrorThe daemon's refusal as a dict: why, request_command, signers, …
from kosa8 import *

def show(label, fn):
    try:
        fn(); print(label, "-> no error")
    except Kosa8Error as e:
        print(f"{label} -> {type(e).__name__} status={e.status}\n   message: {e}\n"
              f"   daemon_message: {e.daemon_message}\n   remediation: {e.remediation}\n   {e.method} {e.path}")

k = Kosa8Client()
show("not found delete", lambda: k.delete_sandbox("doc-py-nope"))
not found delete -> Kosa8NotFoundError status=404
   message: no such sandbox: doc-py-nope (list sandboxes with `kosa8 sandbox ls`)
   daemon_message: no such sandbox: doc-py-nope
   remediation: list sandboxes with `kosa8 sandbox ls`
   DELETE /kosa8/v1/sandboxes/doc-py-nope

Its body was {'error': 'no such sandbox: doc-py-nope', 'remediation': 'list sandboxes with `kosa8 sandbox ls`'}. The other messages below come from the same show helper.

Each class

Kosa8ConnectionError

When: the request never reached a daemon. Any OSError while connecting or sending becomes this: no socket file, a file that is not a socket, a path too long, nothing listening on a TCP port.

kosa8 daemon unreachable at unix:///tmp/no-daemon-here.sock: [Errno 2] No such file or directory (is kosa8 installed and its daemon running? start it with `kosa8 daemon start`)
kosa8 daemon unreachable at http://127.0.0.1:1: [Errno 61] Connection refused (is kosa8 installed and its daemon running? start it with `kosa8 daemon start`)
kosa8 daemon unreachable at unix:///etc/hosts: [Errno 38] Socket operation on non-socket
kosa8 daemon unreachable at unix:///tmp/xxxx…xxxx/kosa8.sock: AF_UNIX path too long

What to do: start the daemon (kosa8 daemon start, then kosa8 daemon status) or fix the address. e.__cause__ is the original OSError; e.__cause__.errno is 2 (no file) or 61 (refused) on macOS. Nothing was done on the daemon side, so the call is safe to retry once it is up. The "is kosa8 installed" hint is added only for "no such file" and "refused".

Kosa8TimeoutError

When: you set a timeout (on the client or the call) and no bytes arrived for that many seconds, while connecting, waiting for the answer to start, or between two pieces of a streamed answer.

POST /kosa8/v1/sandboxes/doc-py-g1/exec timed out talking to unix:///Users/dhruvit/.kosa8/run/kosa8.sock
POST /kosa8/v1/sandboxes/doc-py-g4/exec timed out mid-stream

What to do: know that the work was not cancelled. In testing, a command that timed out on the client side went on to finish in the sandbox. Before retrying something that is not idempotent (creating a sandbox, taking a snapshot with a fixed id), check whether it happened. To limit how long a command may run, limit it inside the sandbox (["timeout", "30", …]). Details: Timeouts.

Kosa8StreamError

When: a run, exec or attach started streaming and then ended with an error frame instead of an exit code, or the stream stopped with no final frame at all. A container id that does not exist is reported this way, not as a 404:

Kosa8StreamError 'no such container: nope'          # status None, daemon_message 'no such container: nope'
stream ended unexpectedly (no final frame)          # the connection dropped mid-run (from the source)

What to do: e.stdout and e.stderr hold what the command printed before the error; log them. A dropped stream usually means the daemon restarted or the sandbox was deleted or rewound from somewhere else mid-command; check get_sandbox(name). exec_frames never raises this: it yields the error frame for you to inspect.

Kosa8CheckpointError

When: the client-side checkpoint bookkeeping refuses, before any request is sent.

no checkpoints for "doc-py-never" — take one with checkpoint() or `kosa8 checkpoint doc-py-never`
no checkpoint "nope" for sandbox "doc-rb-db"
invalid sandbox name "../etc"

What to do: for the first two, list what exists with list_checkpoints(name) or kosa8 checkpoint ls NAME; to= takes a checkpoint id or a label (case-insensitive). A rewind that raises this has changed nothing: the target is looked up before the sandbox is touched. The name check refuses empty names, ., .., names containing / or \, and names starting with a dot, because the name is a key in the shared checkpoint file.

Two more cases from 0.1.1. The checkpoint file's lock is waited for at most lock_timeout seconds (FileCheckpointStore(path=None, *, lock_timeout=10.0)); tested by holding the lock from another process with lock_timeout=1:

timed out after 1s waiting for …/ck/checkpoints.json.lock; another kosa8 process is holding it

Something else (the CLI, the Mac app, another program) held the lock that long; nothing was written, so retry, or raise lock_timeout. On 0.1.0 the call waited forever. And a rewind that failed halfway is this class too.

Kosa8ProtocolError (0.1.1)

When: a line of a streamed answer (run, exec, exec_frames, attach) is not valid JSON, or its data is not valid base64. (From the source; a working daemon does not send such frames.) The message has the form POST /kosa8/v1/sandboxes/X/exec: the daemon sent a frame that is not valid NDJSON (<reason>): <first 200 bytes of the line>, with method and path set and the decoding error as __cause__. On 0.1.0 a raw json.JSONDecodeError escaped instead.

What to do: check that KOSA8_HOST points at a kosa8 worker and not at a proxy that rewrites or buffers responses, and that client and daemon versions match (kosa8 version). The command may have run; check before retrying.

Plain Kosa8Error: checks before sending

When: the SDK refuses an argument itself. Nothing reaches the daemon.

refusing to talk to 10.0.0.5:7070 over plain http: the token and every request would cross the network unencrypted. Use https://10.0.0.5:7070 (a worker behind a TLS-terminating tunnel or proxy), or set insecure=True / KOSA8_INSECURE=1 if this link is already private and trusted
KOSA8_HOST scheme "ftp" is not supported; use http://, https://, tcp:// or unix://
actor must be human, agent or tool, not 'robot'
an egress allow-list needs at least one host; use egress="deny" for no network
egress_allow already limits the network to the hosts it lists; do not also set egress
give at least two variants; with one there is nothing to compare

The first three come from Kosa8Client(...) itself, so a bad KOSA8_HOST fails at construction, not at the first call. Also from the source: KOSA8_HOST '…' names no host, and from explore, forked 1 branches for 2 variants; see list_sandboxes() for what was created and keep X is not one of the branches (…); all of them were kept. What to do: fix the argument; retrying cannot help.

Kosa8CheckpointError: a rewind that failed halfway

When: rewind deleted the sandbox (that has to happen first, because the restored copy takes its name) and then the restore was refused. (From the source; not provoked for this page.) On 0.1.1 it is a Kosa8CheckpointError with this message, and __cause__ is the daemon's error:

restore failed and doc-py-demo no longer exists (it was deleted to free its name for the restore): <the daemon's error> — the checkpoint is intact; recover with restore_snapshot('doc-py-demo-ckpt-1', name='doc-py-demo')

(0.1.0 raised a plain Kosa8Error saying restore failed and doc-py-demo is now stopped: …, which was wrong: the sandbox has been deleted, not stopped.)

What to do: fix the cause, then run exactly the call the message gives. The snapshot is intact and the checkpoint list was not changed. On a Mac the usual cause is a locked screen: macOS will not restore a saved VM while the screen is locked (see the table below). The sandbox is gone from list_sandboxes() until you restore it; its name is free again but its disk files may remain under ~/.kosa8/sandboxes/, and a create_sandbox with that name then fails (below).

Kosa8BadRequestError (400)

When: the daemon refused a value. Most often an egress rule.

egress allow "*.com": com is a public suffix, under which anyone can register a name, so the wildcard would admit strangers' hosts; allow a domain below it, as in *.example.com
egress policy "sometimes": want any, deny, fakenet, or allow:HOST[,HOST...]

What to do: fix the input; the same request fails the same way. These carry no remediation; the message is the explanation.

Kosa8AuthError (401)

When: an agent token was sent and is not valid, or a remote worker's bearer token is missing or wrong. A bad agent token is never treated as "no token": every call is refused, even health().

agent token is not recognised; mint one with `kosa8 agent token create NAME`

What to do: check KOSA8_AGENT_TOKEN and agent_token= (the environment variable is used when the argument is not given), mint a new token, or for a remote worker set token= / KOSA8_TOKEN. Remember that KOSA8_TOKEN is ignored when you pass host= in code. See Agent identity.

Kosa8PolicyError (403)

When: the org policy forbids the action outright: a registry not on its allow-list, a label rule, a network rule wider than policy allows. (Not provoked for this page, to leave the test machine's policy alone; the JavaScript page shows a real one, policy "…" forbids registry "docker.io" (allowed: ghcr.io), from the same daemon code.)

What to do: do something the policy allows, or ask whoever manages it. kosa8 policy show prints the active policy. Retrying does not help. For an agent, pass the message back as the step's result: it says exactly what is not allowed.

Kosa8ApprovalRequiredError (403)

When: the policy wants named people to approve this exact action, and the call brought no usable approval. The SDK raises this subclass when a 403 body has an approval dict with a string why. (From the source; not provoked for this page.)

from kosa8 import Kosa8ApprovalRequiredError, Kosa8PolicyError

try:
    k.delete_sandbox("runner")
except Kosa8ApprovalRequiredError as e:
    print("why:", e.approval["why"])
    print("ask for it with:", e.approval.get("request_command"))
    print("signers:", e.approval.get("signers"))
    approval_id = input("approval id: ")              # from a person, out of band
    k.delete_sandbox("runner", approval=approval_id)
except Kosa8PolicyError as e:
    print("not allowed:", e)

What to do: show e.approval to a person, wait for the approval id, then repeat the same call with approval=id; it is sent as the X-Kosa8-Approval header. An approval covers one call. Never let an agent invent or fetch its own approval. Note that checkpoint, rewind and delete_sandbox make several requests and pass the same approval to each. See Company rules.

Kosa8NotFoundError (404)

When: no such sandbox.

no such sandbox: doc-py-nope (list sandboxes with `kosa8 sandbox ls`)

What to do: on clean-up paths, ignore it: the thing is already gone. Elsewhere, check the name. get_sandbox returns None instead of raising. Two things that sound like 404s are not: a missing container is a stream error, and a missing snapshot is a 500.

Kosa8ConflictError (409)

When: the request conflicts with the current state.

sandbox doc-py-e2 already exists (choose another name, or remove the existing sandbox with `kosa8 sandbox rm NAME` (`kosa8 sandbox ls` lists them))
sandbox data disk already exists: /Users/dhruvit/.kosa8/sandboxes/doc-py-guide-data.img (name in use?) (choose another name, or remove the existing sandbox with `kosa8 sandbox rm NAME` (`kosa8 sandbox ls` lists them))
snapshot already exists: doc-py-g2-manual (choose another id, or delete the existing snapshot first)
sandbox is not running: doc-py-g2-copy (start it with `kosa8 sandbox up doc-py-g2-copy`)

What to do: for a name in use, pick another name (a random suffix avoids it) or delete the old sandbox. The second message is the awkward one: no sandbox of that name is listed, but its disk files are still on disk, typically after a rewind that failed halfway. If a snapshot of it exists (kosa8 snapshot ls), restore it under that name; otherwise use another name. For "not running", start_sandbox(name); that is a cold boot, so containers do not come back. From 0.1.1, checkpoint handles a snapshot id taken by a concurrent checkpoint itself (it steps to the next number); on 0.1.0 that surfaced as this error (see the table).

Kosa8UnsupportedError (501)

When: the daemon's platform cannot run sandboxes: they need macOS on Apple silicon, or Linux with KVM. (From the source; the test machine supports sandboxes.)

What to do: run on supported hardware, or point the client at a worker that is (KOSA8_HOST). On Linux, check that /dev/kvm exists and that the daemon's user can open it, and run kosa8 doctor.

Kosa8ApiError (any other status)

When: the daemon answered with a status that has no class of its own, in practice 500. Two met while testing:

no such snapshot: doc-py-nope-snap (list snapshots with `kosa8 snapshot ls`)          # restore_snapshot, status 500
guest not ready on vsock port 1024 after 25s (run `kosa8 doctor`; check ~/.kosa8/log/)  # create_sandbox, status 500

What to do: read daemon_message; the class alone says little. The second one happened while the machine was booting many sandboxes for other jobs at once; the same create succeeded when run again a minute later. It is the one error on this page worth retrying automatically, a couple of times with a pause, after deleting any half-made sandbox of that name. If it keeps happening, run kosa8 doctor and read ~/.kosa8/log/kosa8d.log.

A sensible way to catch them

from kosa8 import (Kosa8Error, Kosa8ConnectionError, Kosa8TimeoutError, Kosa8StreamError,
                   Kosa8ApprovalRequiredError, Kosa8PolicyError, Kosa8NotFoundError)

def agent_step(ws, cmd):
    """Run one model-proposed command; return text for the model, raise only if kosa8 is down."""
    try:
        code, out = ws.sh(cmd)
        return f"exit {code}\n{out}"
    except Kosa8TimeoutError:
        return "the command produced no output for 60 s and was abandoned (it may still be running)"
    except Kosa8StreamError as e:
        return f"the command could not finish: {e.daemon_message}\n{e.stdout}{e.stderr}"
    except Kosa8ApprovalRequiredError as e:
        return f"needs a human approval: {e.approval['why']}"
    except Kosa8PolicyError as e:
        return f"refused by policy: {e}"
    # Kosa8ConnectionError, Kosa8NotFoundError (sandbox gone) and the rest propagate:
    # the loop cannot continue, so stop it and clean up.

(A pattern; ws is the Workspace from the developer guide.)

Troubleshooting

SymptomCauseFix
Kosa8ConnectionError … [Errno 2] No such file or directoryThe daemon is not running, or HOME is different from the daemon's (the socket is $HOME/.kosa8/run/kosa8.sock).kosa8 daemon start. Under a service manager or in CI, check HOME, or pass socket_path=.
… [Errno 61] Connection refusedThe socket file is left from a daemon that died, or nothing listens on the TCP port.kosa8 daemon start; for TCP, check the worker's address and port.
… AF_UNIX path too longmacOS limits a unix socket path to 104 bytes. Deep temporary HOME folders in CI or test harnesses hit it.A shorter HOME, or a short symlink to it, for both the daemon and your program.
Kosa8Error: refusing to talk to … over plain http at Kosa8Client()KOSA8_HOST is http:// or a bare host:port to another machine.Use https://. Only for a private, trusted link: insecure=True or KOSA8_INSECURE=1.
Remote worker answers 401 although KOSA8_TOKEN is setYou passed host= in code; KOSA8_TOKEN is read only when the host comes from KOSA8_HOST.Pass token= as well, or use KOSA8_HOST.
Your program talks to the wrong daemonKOSA8_HOST is set in the environment and wins over socket_path.print(k.transport.target); unset it, or pass env={} to ignore the environment.
Kosa8ConflictError: sandbox X already existsThe name is taken, often by an earlier run that was killed before its clean-up.Random suffixes; a clean-up sweep at start; kosa8 sandbox rm X.
sandbox data disk already exists: …-data.img (name in use?)No sandbox of that name is listed, but its disk files remain, usually after a rewind whose restore failed.Restore the checkpoint under that name if kosa8 snapshot ls has one (restore_snapshot(id, name=…)), or use another name.
guest not ready on vsock port 1024 after 25s (500)The VM booted too slowly, typically on a machine busy booting other VMs.Retry after a pause; delete any half-created sandbox first. Persisting: kosa8 doctor, ~/.kosa8/log/.
run fails because the image is missingk.pull(ref) pulls into the host engine, not into a sandbox; run does not pull.sb.pull(ref) (pull_into_sandbox) once per sandbox.
Files written by one run are gone in the nextEach run is a fresh container from the image.start_container once, then exec into it.
Kosa8StreamError: no such container: …The container id is wrong, its process exited, or the sandbox was stopped and started (a cold boot keeps no containers).Check sb.containers(); keep the container's main process alive (sleep 3600); use checkpoints, not stop/start, to keep processes.
Kosa8ConflictError: sandbox is not runningThe sandbox was stopped, or the daemon restarted (which stops every sandbox).start_sandbox(name), then start your containers again.
Kosa8TimeoutError on a long commandThe command printed nothing for timeout seconds. The timeout is per read, not total.Raise the timeout, make the command print progress, or bound it inside: ["timeout", "600", …].
Kosa8TimeoutError on create_sandbox, snapshot or forkA client-wide timeout applies to calls that have no per-call one, and boots and restores can take tens of seconds on a busy machine.Keep the client timeout at None or generous; set short ones per run/exec.
The program in the sandbox kept running after a timeout or Ctrl-CTimeouts and interrupts stop the client waiting; nothing tells the sandbox to stop.Bound commands inside the sandbox, or delete or rewind the sandbox to stop everything.
Kosa8Error: stdin of container … was already sent and is closed (0.1.1), or on 0.1.0 a second write_stdin answers {'ok': True} and the program never sees itThe daemon closes stdin when the first write_stdin body ends; 0.1.1 refuses the second call on the same client and container.Send all input in one call: pass a generator that yields chunks as you produce them.
exec_frames ends with {'done': True, 'ok': True, 'exit': 7}ok means the stream succeeded, not the command.Read exit; use exec and ExecResult.ok if you want the simple answer.
rewind, fork, explore or restore_snapshot fails on a Mac; checkpoints workmacOS refuses to restore a saved VM while the screen is locked (the VM's saved state is protected by a key usable only while the Mac is unlocked).Keep the Mac unlocked while agents run, or run them on Linux. After a failed rewind, run the restore_snapshot(…) its message gives. See the full explanation.
Kosa8ConflictError: snapshot already exists: X-ckpt-N from checkpoint (0.1.0)Two checkpoints of the same sandbox at the same moment picked the same number. Reproduced with two threads on 0.1.0; on 0.1.1 the same test produced ckpt-1 and ckpt-2.Upgrade to 0.1.1, which steps to the next number. On 0.1.0: one checkpoint at a time per sandbox.
Kosa8CheckpointError: timed out after 10s waiting for …checkpoints.json.lock (0.1.1)Another process held the checkpoint file's lock for longer than lock_timeout.Retry; find what holds it (a stuck kosa8 process); or FileCheckpointStore(lock_timeout=60).
kosa8 checkpoint ls does not show checkpoints taken from PythonThe client was given a MemoryCheckpointStore or a FileCheckpointStore with another path, or runs with another HOME.Use the default store; check k.checkpoints.store.
Checkpoints of a deleted sandbox reappear on a new sandbox with the same nameIt was deleted with keep_checkpoints=True, or by a tool that does not clean the list.Delete with the SDK's default, or k.checkpoints.forget(name) and delete the snapshots it returns.
The winner of explore is ""No branch exited 0 without error.Look at each ExploreBranch's exit, output and error. All branches were deleted unless keep_all=True.
Sandboxes named …-explore-NNNNN… are left behindThe program stopped during explore, or keep= named an unknown branch (then every branch is kept).Delete them; ExploreResult.winner is yours to delete too.
python3 -m venv or pip fails with ValueError: invalid literal for int() with base 10: '' on macOSSeen with Homebrew Python 3.14 in a restricted shell where platform.mac_ver() returned empty strings, so pip could not work out the macOS version. Not a problem in the kosa8 package, which is pure Python.Install with uv (uv venv, uv pip install kosa8), which does not run pip, or use another Python. PYTHONPATH=src on a checkout of the source also works: there is nothing to build.
See also

Python developer guide for how each call behaves; API reference: errors for the constructors and error_from_response; When something is wrong for kosa8 doctor.