Guides / Go / Errors and troubleshooting
Go errors and troubleshooting
What can go wrong when you drive kosa8 from Go, what the SDK hands back when it does, and what to do about it. The messages on this page are real: they were printed by the programs below, against a kosa8 0.9.36 daemon. For the calls themselves, see the developer guide and the API reference.
The two programs on this page were run against a real kosa8 daemon 0.9.36 on an Apple silicon
Mac, with github.com/kosa8ai/kosa8-go v0.1.2 from the Go module proxy and go1.26.9
darwin/arm64, and their output is pasted unchanged (the first had also been run on v0.1.0; the
second printed identical output on both). The Exec, ExecFrames,
WriteStdin and capture messages quoted below come from the
developer guide's programs. Everything was run on v0.1.1; the sandbox-free first program and the
usage messages that v0.1.2 reworded were run again on v0.1.2. The sandbox they create was deleted. No org
policy was installed and no remote worker was used, so the policy, approval and authentication
(401) errors were not produced live; what this page says about them comes from reading the
SDK's source, and is marked. The locked-screen message is quoted from our tests of the same daemon
version for the JavaScript pages; it was not
reproduced for this page.
What this is, in plain words
When kosa8 cannot do what your program asked, the SDK returns an error instead of a result. In Go, that is the second value every call returns. Each error says what kind of problem it is (kosa8 is not running, the sandbox does not exist, a company rule forbids it, it took too long), and, when kosa8 itself refused, it carries kosa8's own explanation and its advice about what to do next.
One thing is not an error: a command that runs and fails. If your command exits with
code 1, you get a normal result with ExitCode 1 and err is nil. An error
means the command could not be run at all.
The error type
Every error this package creates is a *kosa8.Error. Two kinds of failure come back
as they are, without that wrapper:
- a cancelled context:
context.Canceled(a context whose deadline passed is a*kosa8.Errorof kindtimeout, see below); - failures of the checkpoint store itself: a file that cannot be read or written, the
lock that cannot be taken (
lock the checkpoint store: …), or any error from aCheckpointStoreyou supplied.
| Field | Set when | Meaning |
|---|---|---|
Kind | always | Which kind of failure; table below. |
Message | always | What Error() returns. For a daemon refusal: "<error> (<remediation>)", the way the CLI prints it. |
Status | the daemon answered with HTTP 400 or above | The HTTP status. 0 otherwise, including for stream errors. |
DaemonMessage | daemon refusals, stream error frames | The daemon's own error text, verbatim. |
Remediation | daemon refusals that include advice | What the daemon says to do. Can be empty (a bad egress value had none). |
Method, Path | HTTP and connection errors, most stream errors | The request, such as POST /kosa8/v1/sandboxes/x/run. |
Body | daemon refusals | The decoded response body (map[string]any or a string). |
Approval | KindApprovalRequired | The daemon's refusal object (it has a why key, and whatever else the daemon sends). |
Stdout, Stderr | KindStream, KindTimeout from Run/Exec/Attach | Output produced before the failure. The returned ExecResult holds the same. |
Err | connection errors, timeouts, decode errors, a failed rewind | The underlying cause; Unwrap returns it, so errors.Is/As see through. |
Kinds and sentinels
Kind | Sentinel | When |
|---|---|---|
connection | ErrConnection | The daemon could not be reached, or the answer could not be read. |
bad_request | ErrBadRequest | HTTP 400: the daemon rejected the arguments. |
auth | ErrAuth | HTTP 401: a remote worker rejected the token. From the source. |
policy | ErrPolicy | HTTP 403: the org or label policy refused. |
approval_required | ErrApprovalRequired | HTTP 403 whose body has an approval object with why: a signed approval would allow it. From the source. |
not_found | ErrNotFound | HTTP 404: no such sandbox or snapshot. |
conflict | ErrConflict | HTTP 409: name or id taken, sandbox not running. |
unsupported | ErrUnsupported | HTTP 501: this daemon or platform cannot do it. |
api | ErrAPI | Any other HTTP error status (500, 503, …), or an answer that could not be decoded. |
stream | ErrStream | A Run/Exec/Attach stream ended with an error frame, a broken frame, or no final frame. |
timeout | ErrTimeout | The context's deadline (or RunOptions.Timeout) passed. |
checkpoint | ErrCheckpoint | Client-side checkpoint bookkeeping: bad name, nothing to rewind to, unknown checkpoint, or a rewind whose restore step failed. |
usage | ErrUsage (new in v0.1.1) | Bad arguments caught before sending. |
(*Error).Is matches a target *Error whose Message is empty
(a sentinel, or &kosa8.Error{Kind: k}) by Kind, with two extra
rules:
errors.Is(err, kosa8.ErrPolicy)is also true forapproval_required: an approval refusal is a policy refusal.errors.Is(err, kosa8.ErrAPI)is true for every HTTP kind:api,bad_request,auth,policy,approval_required,not_found,conflict,unsupported. It is false forstream,timeout,connection,checkpointandusage, even when the stream error was the daemon's own words.
So test the specific kinds first. A handler that covers everything:
res, err := sb.Run(ctx, image, args, nil)
switch {
case err == nil:
// It ran. res.ExitCode says how it went; non-zero is not an error.
case errors.Is(err, context.Canceled):
return err // we were told to stop
case errors.Is(err, kosa8.ErrTimeout):
log.Printf("too slow; output so far: %q", res.Stdout())
case errors.Is(err, kosa8.ErrApprovalRequired):
e, _ := kosa8.AsError(err)
log.Printf("needs approval: %v", e.Approval["why"]) // retry later with RunOptions{Approval: id}
case errors.Is(err, kosa8.ErrPolicy):
return err // a rule says no; do not retry
case errors.Is(err, kosa8.ErrNotFound):
// the sandbox is gone: recreate it, or give up
case errors.Is(err, kosa8.ErrConflict):
// e.g. "sandbox is not running": sb.Start(ctx), then retry once
case errors.Is(err, kosa8.ErrStream):
// the command could not start (no image, no such executable, no container)
case errors.Is(err, kosa8.ErrConnection):
// kosa8 is not running: tell the user to run `kosa8 daemon start`
case errors.Is(err, kosa8.ErrUsage):
panic(err) // a bug in our own arguments
default:
return err
}
For logs, err.Error() is already the full sentence with the daemon's advice.
kosa8.AsError(err) is errors.As into *kosa8.Error, for
reading the fields; var e *kosa8.Error; errors.As(err, &e) does the same.
Every error, provoked
The first program needs no sandbox. Its show function prints the kind, the status,
which sentinels match, and the fields:
package main
import (
"context"
"errors"
"fmt"
"strings"
"time"
kosa8 "github.com/kosa8ai/kosa8-go"
)
func show(label string, err error) {
fmt.Printf("== %s\n", label)
if err == nil {
fmt.Println(" nil")
return
}
e, ok := kosa8.AsError(err)
if !ok {
fmt.Printf(" not a *kosa8.Error: %T %v (Canceled=%v)\n", err, err, errors.Is(err, context.Canceled))
return
}
var is []string
for n, s := range map[string]error{"ErrConnection": kosa8.ErrConnection, "ErrAPI": kosa8.ErrAPI, "ErrBadRequest": kosa8.ErrBadRequest,
"ErrPolicy": kosa8.ErrPolicy, "ErrNotFound": kosa8.ErrNotFound, "ErrConflict": kosa8.ErrConflict, "ErrStream": kosa8.ErrStream,
"ErrCheckpoint": kosa8.ErrCheckpoint, "ErrTimeout": kosa8.ErrTimeout, "ErrUnsupported": kosa8.ErrUnsupported, "ErrAuth": kosa8.ErrAuth, "ErrUsage": kosa8.ErrUsage} {
if errors.Is(err, s) {
is = append(is, n)
}
}
fmt.Printf(" Kind=%s Status=%d errors.Is: %s\n Message: %s\n", e.Kind, e.Status, strings.Join(sorted(is), ", "), e.Message)
if e.DaemonMessage != "" {
fmt.Printf(" DaemonMessage: %s\n Remediation: %s\n", e.DaemonMessage, e.Remediation)
}
if e.Method != "" {
fmt.Printf(" %s %s\n", e.Method, e.Path)
}
if e.Err != nil {
fmt.Printf(" Err: %T\n", e.Err)
}
}
func sorted(a []string) []string {
for i := range a {
for j := i + 1; j < len(a); j++ {
if a[j] < a[i] {
a[i], a[j] = a[j], a[i]
}
}
}
return a
}
func main() {
ctx := context.Background()
gone, _ := kosa8.New(kosa8.Options{SocketPath: "/tmp/k8-nowhere.sock"})
_, err := gone.Version(ctx)
show("daemon not reachable", err)
k, _ := kosa8.New(kosa8.Options{})
_, err = k.CreateSandbox(ctx, kosa8.CreateOptions{Egress: "any", EgressAllow: []string{"pypi.org"}})
show("EgressAllow with Egress any", err)
_, err = k.Explore(ctx, "x", []string{"true"}, kosa8.ExploreOptions{})
show("Explore with one variant", err)
_, err = k.CreateSandbox(ctx, kosa8.CreateOptions{Name: "doc-go-g-bad", Egress: "sometimes"})
show("bad egress value", err)
_, err = k.CreateSandbox(ctx, kosa8.CreateOptions{Name: "doc-go-g-bad", Mounts: []string{"/tmp"}})
show("a mount with no guest path", err)
_, err = k.StopSandbox(ctx, "doc-go-g-nope")
show("StopSandbox on a missing sandbox", err)
_, err = k.Run(ctx, "doc-go-g-nope", "alpine:latest", []string{"true"}, nil)
show("Run on a missing sandbox", err)
_, err = k.DeleteSnapshot(ctx, "doc-go-g-nope-snap", kosa8.SnapshotDeleteOptions{})
show("DeleteSnapshot missing", err)
_, err = k.RestoreSnapshot(ctx, "doc-go-g-nope-snap", kosa8.RestoreOptions{})
show("RestoreSnapshot missing", err)
_, err = k.DeleteSandbox(ctx, "doc-go-g-nope", kosa8.DeleteOptions{})
show("DeleteSandbox missing", err)
_, err = k.Rewind(ctx, "doc-go-g-never", "")
show("Rewind with no checkpoints", err)
_, err = k.Checkpoint(ctx, "../etc", "x")
show("Checkpoint bad name", err)
tctx, cancel := context.WithTimeout(ctx, time.Nanosecond)
defer cancel()
time.Sleep(time.Millisecond)
_, err = k.Health(tctx)
show("expired deadline", err)
cctx, cancel2 := context.WithCancel(ctx)
cancel2()
_, err = k.Health(cctx)
show("cancelled context", err)
}
$ go run .
== daemon not reachable
Kind=connection Status=0 errors.Is: ErrConnection
Message: kosa8 daemon unreachable at unix:///tmp/k8-nowhere.sock: Get "http://kosa8/kosa8/v1/health": dial unix /tmp/k8-nowhere.sock: connect: no such file or directory (is kosa8 installed and its daemon running? start it with `kosa8 daemon start`)
GET /kosa8/v1/health
Err: *url.Error
== EgressAllow with Egress any
Kind=usage Status=0 errors.Is: ErrUsage
Message: EgressAllow already limits the network to the hosts it lists; do not also set Egress
== Explore with one variant
Kind=usage Status=0 errors.Is: ErrUsage
Message: give at least two variants; with one there is nothing to compare
== bad egress value
Kind=bad_request Status=400 errors.Is: ErrAPI, ErrBadRequest
Message: egress policy "sometimes": want any, deny, fakenet, or allow:HOST[,HOST...]
DaemonMessage: egress policy "sometimes": want any, deny, fakenet, or allow:HOST[,HOST...]
Remediation:
POST /kosa8/v1/sandboxes
== a mount with no guest path
Kind=usage Status=0 errors.Is: ErrUsage
Message: mount "/tmp": want host:guest or host:guest:ro
== StopSandbox on a missing sandbox
Kind=not_found Status=404 errors.Is: ErrAPI, ErrNotFound
Message: no such sandbox: doc-go-g-nope (list sandboxes with `kosa8 sandbox ls`)
DaemonMessage: no such sandbox: doc-go-g-nope
Remediation: list sandboxes with `kosa8 sandbox ls`
POST /kosa8/v1/sandboxes/doc-go-g-nope/down
== Run on a missing sandbox
Kind=not_found Status=404 errors.Is: ErrAPI, ErrNotFound
Message: no such sandbox: doc-go-g-nope (list sandboxes with `kosa8 sandbox ls`)
DaemonMessage: no such sandbox: doc-go-g-nope
Remediation: list sandboxes with `kosa8 sandbox ls`
POST /kosa8/v1/sandboxes/doc-go-g-nope/run
== DeleteSnapshot missing
Kind=not_found Status=404 errors.Is: ErrAPI, ErrNotFound
Message: no such snapshot: doc-go-g-nope-snap (list snapshots with `kosa8 snapshot ls`)
DaemonMessage: no such snapshot: doc-go-g-nope-snap
Remediation: list snapshots with `kosa8 snapshot ls`
DELETE /kosa8/v1/snapshots/doc-go-g-nope-snap
== RestoreSnapshot missing
Kind=api Status=500 errors.Is: ErrAPI
Message: no such snapshot: doc-go-g-nope-snap (list snapshots with `kosa8 snapshot ls`)
DaemonMessage: no such snapshot: doc-go-g-nope-snap
Remediation: list snapshots with `kosa8 snapshot ls`
POST /kosa8/v1/snapshots/doc-go-g-nope-snap/restore
== DeleteSandbox missing
Kind=not_found Status=404 errors.Is: ErrAPI, ErrNotFound
Message: no such sandbox: doc-go-g-nope (list sandboxes with `kosa8 sandbox ls`)
DaemonMessage: no such sandbox: doc-go-g-nope
Remediation: list sandboxes with `kosa8 sandbox ls`
DELETE /kosa8/v1/sandboxes/doc-go-g-nope
== Rewind with no checkpoints
Kind=checkpoint Status=0 errors.Is: ErrCheckpoint
Message: no checkpoints for "doc-go-g-never" — take one with Checkpoint or `kosa8 checkpoint doc-go-g-never`
== Checkpoint bad name
Kind=checkpoint Status=0 errors.Is: ErrCheckpoint
Message: invalid sandbox name "../etc"
== expired deadline
Kind=timeout Status=0 errors.Is: ErrTimeout
Message: GET /kosa8/v1/health timed out talking to unix:///Users/dhruvit/.kosa8/run/kosa8.sock
GET /kosa8/v1/health
Err: *url.Error
== cancelled context
not a *kosa8.Error: *errors.errorString context canceled (Canceled=true)
An earlier version of this program also created a sandbox named "bad name!",
expecting a refusal. The daemon accepted the name (on both SDK versions), so that case was dropped
and the sandbox deleted. Worth knowing from this output:
- A missing snapshot is a 404 for
DeleteSnapshotbut a 500 forRestoreSnapshot(kindapi, notnot_found), with the same message. Code that restores should not rely onErrNotFoundalone; checkDaemonMessageorListSnapshotsfirst. - A malformed mount is caught by the SDK (
KindUsage). In v0.1.0 every mount, well-formed or not, reached the daemon as a string and failed with a 500mount has no guest path; v0.1.1 sends the objects the daemon takes, and mounts work.
The second program creates one sandbox and gets it into trouble:
func show(label string, err error) {
fmt.Printf("== %s\n", label)
e, ok := kosa8.AsError(err)
if !ok {
fmt.Printf(" %T %v\n", err, err)
return
}
fmt.Printf(" Kind=%s Status=%d ErrAPI=%v\n Message: %s\n", e.Kind, e.Status, errors.Is(err, kosa8.ErrAPI), e.Message)
if e.Stdout != "" || e.Stderr != "" {
fmt.Printf(" Stdout=%q Stderr=%q\n", e.Stdout, e.Stderr)
}
}
func main() {
ctx := context.Background()
k, _ := kosa8.New(kosa8.Options{})
const name = "doc-go-g-errs"
if _, err := k.CreateSandbox(ctx, kosa8.CreateOptions{Name: name, Egress: "deny"}); err != nil {
show("create", err)
os.Exit(1)
}
defer k.DeleteSandbox(context.Background(), name, kosa8.DeleteOptions{})
_, err := k.CreateSandbox(ctx, kosa8.CreateOptions{Name: name})
show("create the same name twice", err)
_, err = k.Run(ctx, name, "alpine:latest", []string{"true"}, nil)
show("Run before PullIntoSandbox", err)
_, err = k.PullIntoSandbox(ctx, name, "docker.io/library/no-such-image-kosa8:v0")
show("pull an image that does not exist", err)
_, _ = k.PullIntoSandbox(ctx, name, "alpine:latest")
_, err = k.Run(ctx, name, "alpine:latest", []string{"no-such-binary"}, nil)
show("Run a command that does not exist", err)
_, err = k.Exec(ctx, name, "k99-nope", []string{"true"}, nil)
show("Exec in a container that does not exist", err)
_, err = k.Attach(ctx, name, "k99-nope", nil)
show("Attach to a container that does not exist", err)
_, err = k.Fork(ctx, name, 1, kosa8.ForkOptions{Label: "experiment"})
show("Fork with a free-text Label", err)
_, err = k.Snapshot(ctx, name, kosa8.SnapshotOptions{ID: "doc-go-g-errs-s1"})
show("first snapshot", err)
_, err = k.Snapshot(ctx, name, kosa8.SnapshotOptions{ID: "doc-go-g-errs-s1"})
show("snapshot id taken twice", err)
_, err = k.DeleteSnapshot(ctx, "doc-go-g-errs-s1", kosa8.SnapshotDeleteOptions{})
show("delete snapshot", err)
_, err = k.StopSandbox(ctx, name)
_, err = k.Run(ctx, name, "alpine:latest", []string{"true"}, nil)
show("Run on a stopped sandbox", err)
_, err = k.Snapshot(ctx, name, kosa8.SnapshotOptions{})
show("Snapshot a stopped sandbox", err)
}
$ go run .
== create the same name twice
Kind=conflict Status=409 ErrAPI=true
Message: sandbox doc-go-g-errs already exists (choose another name, or remove the existing sandbox with `kosa8 sandbox rm NAME` (`kosa8 sandbox ls` lists them))
== Run before PullIntoSandbox
Kind=stream Status=0 ErrAPI=false
Message: image "docker.io/library/alpine:latest" not found in engine: image "docker.io/library/alpine:latest": not found
== pull an image that does not exist
Kind=api Status=500 ErrAPI=true
Message: pull : manifest: unauthorized after token refresh (check the image reference and network)
== Run a command that does not exist
Kind=stream Status=0 ErrAPI=false
Message: failed to create shim task: OCI runtime create failed: executable file `no-such-binary` not found in $PATH: No such file or directory
== Exec in a container that does not exist
Kind=stream Status=0 ErrAPI=false
Message: no such container: k99-nope
== Attach to a container that does not exist
Kind=stream Status=0 ErrAPI=false
Message: no such container: k99-nope
== Fork with a free-text Label
Kind=policy Status=403 ErrAPI=true
Message: refused by label policy: label "experiment": level "EXPERIMENT" is not defined (defined: UNCLASSIFIED, CUI); an org policy defines levels under "labels" (a fork keeps its parent's label or raises it; see `kosa8 label ls`)
== first snapshot
<nil> <nil>
== snapshot id taken twice
Kind=conflict Status=409 ErrAPI=true
Message: snapshot already exists: doc-go-g-errs-s1 (choose another id, or delete the existing snapshot first)
== delete snapshot
<nil> <nil>
== Run on a stopped sandbox
Kind=conflict Status=409 ErrAPI=true
Message: sandbox is not running: doc-go-g-errs (start it with `kosa8 sandbox up doc-go-g-errs`)
== Snapshot a stopped sandbox
Kind=api Status=500 ErrAPI=true
Message: sandbox is not running: doc-go-g-errs (sandbox must be running)
(<nil> <nil> is how this show prints a call that
succeeded.) Note the same condition, "sandbox is not running", is a 409 for Run and a
500 for Snapshot; errors.Is(err, kosa8.ErrConflict) only catches the
first.
Each kind: when, and what to do
connection: the daemon cannot be reached
When. The socket file does not exist, nothing is listening on it, a remote worker does not
answer, or the answer was cut off before it could be read. The message names the endpoint, and adds
(is kosa8 installed and its daemon running? …) when the cause looks like "not running".
Err is the *url.Error from net/http.
What to do. kosa8 daemon start, then kosa8 version. Check that
your program and the daemon agree on HOME, since the socket is
$HOME/.kosa8/run/kosa8.sock. If KOSA8_HOST is set in your shell, the SDK
goes there instead; k.Endpoint().Target() prints where it is going. Retrying is
reasonable only right after a daemon restart.
usage: caught before sending
When. From New: an Actor other than human,
agent, tool; a KOSA8_HOST/Host with an unsupported
scheme or no host name; plain http:// to a host that is not loopback without
Insecure or KOSA8_INSECURE=1. From calls: EgressAllow with an
Egress other than ""/"deny"; Explore with fewer
than two variants, a fork that returned fewer branches than variants, or a Keep that
names no branch (that one also returns the result, with every branch kept); a malformed or
relative Mounts entry; a Capture with no Path:
mount "hostdir:/work": both paths must be absolute (use filepath.Abs for the host side) Capture.Path is required: the host file the capture is written to
New in v0.1.1, two mistakes that used to pass silently:
Exec does not take Env: the container keeps the environment it was started with. Set it at StartInteractive (InteractiveOptions.Env), or wrap the command: []string{"env", "K=V", "cmd"}
stdin of container k8-e5df7b9b in doc-go-g-run was already sent and is closed: the daemon treats one WriteStdin body as the whole of stdin. Pass an io.Pipe reader to stream input
The first is Exec with RunOptions.Env (the daemon's exec takes no
environment; use []string{"env", "K=V", "cmd", …}, or start the container with
StartInteractive and its InteractiveOptions.Env). The second is a second WriteStdin to the same
container from the same Client. Other real messages are in the
guide and the output above.
What to do. Fix the arguments; these never succeed on retry. Test with
errors.Is(err, kosa8.ErrUsage) (v0.1.0 had no sentinel; e.Kind ==
kosa8.KindUsage works in both).
bad_request (400)
When. The daemon rejected a value, such as an unknown egress policy. What to do. Fix
the value; read DaemonMessage, which says what it wanted. Not retryable.
auth (401)
When (from the source; not produced live). A remote worker rejected or did not get the
bearer token. The common cause is setting Options.Host in code while the token is only
in KOSA8_TOKEN: the env token is used only with an env host. What to do. Pass
Token explicitly, or set both KOSA8_HOST and KOSA8_TOKEN and
leave Host empty. A unix socket never sends a token.
policy and approval_required (403)
When. A rule refused the action. Live, we got one from the label policy, by passing a free
text ForkOptions.Label (above): Label on forks and restores is a
classification level, not a description. The other 403s (org policy rules on registries, egress,
mounts, and so on, and refusals that a signed approval would lift) were not produced live, since no
policy was installed. From the source: a 403 whose body has an approval object with a
why key becomes approval_required, with that object in
e.Approval; anything else is policy.
What to do. For policy, do not retry: change what you ask for, or ask whoever
owns the policy (kosa8 policy show, Company rules).
For approval_required, show the user e.Approval["why"] and the
remediation, get the approval recorded, then repeat the call with its id in
Call{Approval: id} or the options' Approval field.
if errors.Is(err, kosa8.ErrApprovalRequired) {
e, _ := kosa8.AsError(err)
fmt.Println("needs approval:", e.Approval["why"])
fmt.Println("then:", e.Remediation)
return err
}
not_found (404)
When. No sandbox or snapshot by that name, for most calls. Exceptions seen live:
RestoreSnapshot of a missing snapshot is a 500 (api); and
Exec/Attach on a missing container is a stream error.
GetSandbox never returns not_found: a missing sandbox is nil, nil.
What to do. In clean-up code, treat it as success
(err != nil && !errors.Is(err, kosa8.ErrNotFound)). Elsewhere, it usually means
something else deleted the sandbox, or a rewind failed halfway (see below).
conflict (409)
When. A sandbox name or snapshot id is taken; the sandbox is stopped and you asked it to
run something. Checkpoint handles its own case: when two checkpoints of one sandbox
race for the same id, the loser's snapshot gets snapshot already exists and, from
v0.1.1, moves to the next number (up to 10 tries), so you do not see it (see
Goroutines). Any other 409 from that snapshot is
returned. In v0.1.0 this race came back to you as:
snapshot already exists: doc-go-g-tick-ckpt-2 (choose another id, or delete the existing snapshot first)
What to do. Pick another name, delete the old one, or sb.Start(ctx) and
retry.
unsupported (501)
When (from the source). The daemon does not implement the call on this platform or
version. What to do. Upgrade kosa8 (kosa8 version shows both sides), or use
another way to get the same result.
api: everything else
When. Any other HTTP error, mostly 500s with the daemon's own explanation: the guest did
not boot in time, a pull failed, a restore failed, a snapshot of a stopped sandbox. From kosa8
0.9.36 a restore refused because a Mac's screen is locked is a 503. Also used when a successful
answer could not be decoded (decode …, with Err set).
What to do. Read Message; the daemon's remediation is in it. Many of these are
about the machine (memory, disk, a locked screen), so a retry after fixing that can work. See the
table below.
stream: the command could not run
When. Run, Exec and Attach answer with a stream of
frames, so the HTTP status is already 200 by the time the daemon finds out the command cannot start.
It then sends a final frame with an error, and the SDK returns
KindStream with Status 0. Seen live: image not pulled into the sandbox, an
executable that does not exist, a container id that does not exist. The SDK also returns
KindStream for a frame it cannot decode, and for a stream that ends without a final
frame (stream ended unexpectedly (no final frame)), which is what a daemon restart in the
middle of a run looks like. Stdout/Stderr on the error hold any output that
arrived first.
What to do. Pull the image with sb.Pull; check the command's spelling (a
missing executable is an error, not exit 127); check the container id with
sb.Containers. Remember errors.Is(err, kosa8.ErrAPI) is false for these.
ExecFrames passes the error frame to your function (f.Done and
f.Error set) and then returns the same KindStream error, from v0.1.1
(v0.1.0 returned nil):
frame done=true error="no such container: k99-nope" ExecFrames on a missing container: true no such container: k99-nope
timeout, and cancellation
When. The context's deadline passed, or RunOptions.Timeout did. Before the
answer started: <METHOD> <path> timed out talking to <endpoint>, with
Err wrapping context.DeadlineExceeded. During a stream: … timed out
mid-stream, with the output so far. A cancelled context returns the plain
context.Canceled, not a *kosa8.Error.
What to do. Either way, the work inside the sandbox keeps running: only your
program stopped waiting. In our test, two sleep 30 containers whose Run
had timed out or been cancelled were still listed as running afterwards. Stop or delete the sandbox
if that matters, or bound the command itself (timeout 30 …). Give long operations
(create, fork, explore) generous deadlines: on our busy machine a create took 12 to 14 s, the
quickstart's took 21 s, and an explore with three variants took 87.5 s.
checkpoint: bookkeeping, and a failed rewind
When. All before anything is sent, except the last:
invalid sandbox name "../etc": empty,.,.., a leading dot, or a/or\in the name.no checkpoints for "NAME" — take one with Checkpoint or `kosa8 checkpoint NAME`:Rewind(ctx, name, "")with an empty stack.no checkpoint "step 1" for sandbox "NAME":tomatches no id or label, for example one dropped by an earlier rewind.restore failed and NAME no longer exists (it was deleted to free its name for the restore): … — the checkpoint is intact; recover with RestoreSnapshot("ID", RestoreOptions{Name: "NAME"}): the rewind deleted the sandbox, then could not restore it.Erris the daemon's error, soerrors.Is(err, kosa8.ErrAPI)is also true here when the daemon answered. (v0.1.0 said "is now stopped", which was wrong.) From the source; not produced live.
What to do. For the last one, fix the cause (usually a locked Mac screen, see below), then run exactly the call in the message:
if errors.Is(err, kosa8.ErrCheckpoint) && errors.Is(err, kosa8.ErrAPI) {
// Rewind removed the sandbox and could not restore it. After fixing the cause:
_, err = k.RestoreSnapshot(ctx, target.ID, kosa8.RestoreOptions{Name: sandbox})
}
Here target is the checkpoint you rewound to (from ListCheckpoints
before the call). The checkpoint records are left as they were, so after a successful manual
restore, later checkpoints are still in the stack.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
kosa8 daemon unreachable at unix://…: … no such file or directory | The daemon is not running, or runs under another HOME. | kosa8 daemon start; run your program with the same HOME. |
Connection error, bind: invalid argument in the daemon log (macOS) | Socket path over 104 bytes: HOME is a deep temporary folder (CI, test harnesses). | Use a short HOME or a symlink to it, for the daemon and your program. See the JavaScript notes. |
guest not ready on vsock port 1024 after 25s (run `kosa8 doctor`; check ~/.kosa8/log/) from CreateSandbox | The VM did not boot in time; in our tests, low free memory with several other sandboxes running. | Delete sandboxes you do not need, give new ones less MemMB, and retry after a pause (recipe). Nothing was left behind by the failed create. |
image "…" not found in engine (KindStream) | The image was not pulled into this sandbox. k.Pull pulls into the host, not a sandbox. | sb.Pull(ctx, image) first. Forks inherit their parent's images. |
pull : manifest: unauthorized after token refresh | The image does not exist, or is private. | Check the reference (name and tag) with kosa8 pull on the host. |
executable file `x` not found in $PATH | The program is not in the image. It is an error, not exit 127. | Use a full path, another image, or sh -c for shell builtins. |
no such container: k… | Wrong id, or the container exited, or the sandbox was restarted (containers do not survive a stop). | sb.Containers(ctx); start a new one. |
Exec does not take Env (KindUsage) | The daemon's exec has no environment; v0.1.0 silently dropped it. | Wrap the command: []string{"env", "FOO=bar", "sh", "-c", "…"}. |
mount "…": want host:guest or host:guest:ro, or both paths must be absolute | A Mounts entry without both parts, with a suffix other than ro/rw, or (v0.1.2) with a relative path. | "/abs/host/dir:/guest/dir:ro". (On v0.1.0, every mount failed with mount has no guest path; upgrade.) |
capture: open …: is a directory (500) | CaptureOptions.Path is the pcapng file to write, not a directory. | Pass a file path, such as dir/traffic.pcapng. |
stdin of container … was already sent and is closed | A second WriteStdin: one body is the whole of stdin. | One call per container, with an io.Pipe reader to feed input over time. |
refused by label policy: label "…": level "…" is not defined | ForkOptions.Label/RestoreOptions.Label is a classification level. | Leave it empty, or use a level from kosa8 label ls. Use CreateOptions.Labels for tags. |
sandbox … already exists (409) | The name is taken, often by a sandbox an earlier run did not delete. | Delete it, or use unique names; see the clean-up recipe. |
snapshot already exists: NAME-ckpt-N from Checkpoint | v0.1.0: two checkpoints of one sandbox at the same time. | Upgrade to v0.1.1, which steps to the next id. |
sandbox is not running (409 or 500) | The sandbox was stopped, by you or by a daemon restart. | sb.Start(ctx); running containers are gone after a stop. |
macOS blocks restoring a VM while the screen is locked; unlock and retry (503), from Rewind, Fork, Explore, RestoreSnapshot | Apple's Virtualization framework cannot restore a saved VM while the Mac is locked. Saving still works. | Keep the Mac unlocked during jobs that restore, or use Linux. After a failed rewind, run the RestoreSnapshot call from its message. Details on the JavaScript troubleshooting page. |
… timed out mid-stream, but the work goes on | Timeouts and cancellation stop the client only. | Stop or delete the sandbox, or bound the command with timeout. |
kosa8 rewind does not see checkpoints taken in Go | The client uses a private store (Options.Checkpoints), or a different HOME. | Use the default store; check ~/.kosa8/checkpoints.json. |
401 from a remote worker though KOSA8_TOKEN is set | Options.Host was set in code; the env token is only used with KOSA8_HOST. | Pass Token as well. |
refusing to talk to HOST over plain http | Plain HTTP to a non-loopback worker. | Use https://, or Insecure: true / KOSA8_INSECURE=1 on a private link. |
| Leftover sandboxes after a crash or Ctrl-C | The program exited without running its deferred deletes (os.Exit, log.Fatal, a signal). | signal.NotifyContext and defers with a fresh context, as in the recipe; clean up with kosa8 sandbox ls / kosa8 snapshot ls. |
kosa8 doctor checks the installation, and ~/.kosa8/log/kosa8d.log has
the daemon's side of every refusal. When something is wrong
covers the daemon itself. The developer guide shows each call
working.