JuliusBrussee/caveman · error

native session key length = %d, want %d

Error message

native session key length = %d, want %d

What it means

The existing session.key was read but its length is not the expected 32 bytes (sessionKeyBytes). The file was truncated, appended to, or hand-edited. Because HMAC markers minted with a wrong-length key would not match the markerPattern/signature contract, the mismatch is fatal rather than self-healed — regenerating would silently invalidate all in-flight markers.

Source

Thrown at proxy/internal/nativeruntime/marker.go:61

		if syncErr := file.Sync(); syncErr != nil {
			_ = file.Close()
			_ = os.Remove(path)
			return nil, fmt.Errorf("native session key sync: %w", syncErr)
		}
		if closeErr := file.Close(); closeErr != nil {
			return nil, fmt.Errorf("native session key close: %w", closeErr)
		}
		return key, nil
	}
	if !errors.Is(err, os.ErrExist) {
		return nil, fmt.Errorf("native session key create: %w", err)
	}
	key, err = os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("native session key read: %w", err)
	}
	if len(key) != sessionKeyBytes {
		return nil, fmt.Errorf("native session key length = %d, want %d", len(key), sessionKeyBytes)
	}
	if err := os.Chmod(path, 0o600); err != nil {
		return nil, fmt.Errorf("native session key chmod: %w", err)
	}
	return key, nil
}

// SessionMarker builds model-temporary correlation context. Local proxy removes
// valid markers byte-surgically before provider inspection or forwarding.
func SessionMarker(key []byte, sessionID string) (string, error) {
	if len(key) != sessionKeyBytes || sessionID == "" || len(sessionID) > 256 {
		return "", errors.New("native session marker: invalid key or session id")
	}
	encoded := base64.RawURLEncoding.EncodeToString([]byte(sessionID))
	sig := markerMAC(key, encoded)
	return fmt.Sprintf(`[[caveman-session-v1 sid="%s" sig="%s"]]`, encoded, sig), nil
}

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Remove the bad file (rm <home>/runtime/session.key) so the next start generates a fresh 32-byte key — note active session markers signed with the old key become invalid
  2. Write keys byte-exact if provisioning externally: printf '%s' or dd, never echo
  3. Confirm the file is exactly 32 bytes: wc -c <home>/runtime/session.key

Example fix

# before
$ wc -c ~/.caveman/runtime/session.key
33   # trailing newline -> Error[1074]

# after
$ rm ~/.caveman/runtime/session.key   # regenerated as exactly 32 bytes on next start
Defensive patterns

Strategy: validation

Validate before calling

func keyFileSane(path string) bool {
    fi, err := os.Stat(path)
    return err == nil && fi.Mode().IsRegular() && fi.Size() == 32
}

kp := filepath.Join(home, "runtime", "session.key")
if !keyFileSane(kp) { /* regenerate deliberately, knowing old markers die */ }

Prevention

When it happens

Trigger: session.key is 0 bytes (truncated by disk-full during an old write), 33+ bytes (accidental append, echo adding a newline), or replaced by a text file; key written by a tool that appended a trailing newline.

Common situations: Operators provisioning keys with echo/printf which add '\n'; earlier disk-full incidents; configuration-management tools overwriting the file.

Related errors


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