JuliusBrussee/caveman · error

native runtime: session id is required

Error message

native runtime: session id is required

What it means

Runtime.Handle validates each request before processing; a request whose Session.ID is blank after trimming is rejected because all runtime state (decisions, CCR objects, receipts) is keyed by session. This check runs after protocol-version and event-type validation, under the runtime's mutex.

Source

Thrown at proxy/internal/nativeruntime/runtime.go:216

		return profile, profileFeatures{taskContract: true, compactState: true, reuse: true, capture: true, mask: true, repository: true}, nil
	default:
		return "", profileFeatures{}, fmt.Errorf("native runtime: unknown profile %q", raw)
	}
}

func (r *Runtime) Handle(_ context.Context, request Request) (Response, error) {
	started := time.Now()
	r.mu.Lock()
	defer r.mu.Unlock()

	if request.ProtocolVersion != ProtocolVersion {
		return Response{}, fmt.Errorf("native runtime: unsupported protocol version %d", request.ProtocolVersion)
	}
	if _, ok := eventTypes[request.Event.Type]; !ok {
		return Response{}, fmt.Errorf("native runtime: unknown event %q", request.Event.Type)
	}
	if strings.TrimSpace(request.Session.ID) == "" {
		return Response{}, errors.New("native runtime: session id is required")
	}
	policyMode := request.PolicyMode
	if policyMode == "" {
		policyMode = "safe"
	}
	if policyMode != "record" && policyMode != "safe" && policyMode != "max" {
		return Response{}, fmt.Errorf("native runtime: unknown policy mode %q", policyMode)
	}
	request.PolicyMode = policyMode
	profile, features, err := resolveProfile(request.Profile, policyMode)
	if err != nil {
		return Response{}, err
	}
	request.Profile = profile
	r.lastActivity = time.Now()
	if request.Event.Type == "session.end" {
		delete(r.activeSessions, request.Session.ID)
	} else {

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Populate request.Session.ID with a stable non-empty session identifier before sending
  2. Check the client's JSON tags against the protocol's `session.id` path
  3. Allocate/persist a session id before the first Handle call and reuse it for the session's lifetime

Example fix

// before
resp, err := rt.Handle(ctx, nativeruntime.Request{Event: evt}) // Session.ID empty

// after
resp, err := rt.Handle(ctx, nativeruntime.Request{
    Session: nativeruntime.Session{ID: sessionID},
    Event:   evt,
})
Defensive patterns

Strategy: validation

Validate before calling

if strings.TrimSpace(request.Session.ID) == "" {
    return errors.New("allocate a session id before Handle")
}
resp, err := rt.Handle(ctx, request)

Type guard

func hasSession(r nativeruntime.Request) bool {
    return strings.TrimSpace(r.Session.ID) != ""
}

Prevention

When it happens

Trigger: Sending a Request over the unix socket/named pipe with session.id omitted, empty, or whitespace; a client struct where the json tag for the session id doesn't match the protocol field name.

Common situations: First-request bootstrapping code that hasn't allocated a session yet; JSON field-name mismatch (sessionId vs session) in a hand-rolled client; tests constructing Request literals without Session.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/73004008596034a7. Report an issue: GitHub.