JuliusBrussee/caveman · error
invalid run-state contract
Error message
invalid run-state contract
What it means
runstate.read loads the JSON state file for a given home/port and enforces its contract: schema must equal Schema, port must equal the file's own port, PID >= 1, instance token non-empty, and owner exactly "wrap" or "start". Any violation means the file is truncated, corrupt, forged, or from an incompatible version, and it is rejected outright.
Source
Thrown at proxy/internal/runstate/runstate.go:140
}
if err := tmp.Close(); err != nil {
return err
}
return os.Rename(tmpName, Path(home, state.Port))
}
func read(home string, port int) (State, error) {
raw, err := os.ReadFile(Path(home, port))
if err != nil {
return State{}, err
}
var state State
if err := json.Unmarshal(raw, &state); err != nil {
return State{}, err
}
if state.Schema != Schema || state.Port != port || state.PID < 1 ||
state.InstanceToken == "" || (state.Owner != "wrap" && state.Owner != "start") {
return State{}, errors.New("invalid run-state contract")
}
return state, nil
}
type validators struct {
alive func(int) bool
executable func(int) (string, error)
bound func(string) bool
}
func validate(state State, checks validators) bool {
if !checks.alive(state.PID) {
return false
}
exe, err := checks.executable(state.PID)
if err != nil || !strings.Contains(strings.ToLower(filepath.Base(exe)), "caveman-proxy") {
return false
}View on GitHub (pinned to 27d5a3981a)
Solutions
- Delete the invalid state file (it is derived data) and let the owner process rewrite it on next start
- Ensure only one binary version writes to a given home during upgrades
- If writing state yourself, emit all contract fields: schema, port, pid >= 1, instance token, owner of wrap|start
Example fix
// before
state, err := runstate.Read(home, port)
if err != nil { return err }
// after
state, err := runstate.Read(home, port)
if err != nil {
if errors.Is(err, os.ErrNotExist) { /* not running */ return nil }
// invalid contract: treat as not-running and clear the corrupt file
_ = os.Remove(runstate.Path(home, port))
return nil
} Defensive patterns
Strategy: try-catch
Try / catch
state, err := runstate.Read(home, port)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
// no instance running
} else {
// contract violation: treat as stale and remove the corrupt file
_ = os.Remove(runstate.Path(home, port))
}
} Prevention
- Treat run-state files as disposable derived data; delete on contract violations
- Write state atomically (temp file + rename) to avoid truncated writes
- Never hand-edit run-state files
When it happens
Trigger: Reading a run-state file that was partially written (crash mid-write), hand-edited, copied from a different port, produced by a different schema version, or spoofed by another process dropping a file in the home directory.
Common situations: Process killed during state-file write leaving truncated JSON fields that parse but fail the contract; stale state from an older binary after upgrade; users editing the file to change the port or PID.
Related errors
- corpus provider conversion supports string or null content
- cachebench: verifier returned duplicate or invalid JSON
- cachebench: verifier returned invalid JSON
- cacheengine: JSON nesting limit exceeded
- cacheengine: request root must be object
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/afdc83935d024c59.
Report an issue: GitHub.