{"record":{"id":"5b18595e712d1b59","repo":"JuliusBrussee/caveman","slug":"open-arm-q-w","errorCode":null,"errorMessage":"open arm %q: %w","messagePattern":"open arm %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/store/learn_experiment.go","lineNumber":139,"sourceCode":"\tif err != nil {\n\t\treturn Experiment{}, err\n\t}\n\tif experiment.StoppedAt != \"\" {\n\t\treturn Experiment{}, fmt.Errorf(\"experiment %q is stopped\", experiment.Label)\n\t}\n\tfor _, existing := range experiment.Arms {\n\t\tif existing.EndedAt == \"\" && existing.Arm == arm {\n\t\t\treturn experiment, nil\n\t\t}\n\t}\n\tnow := learnOutcomeClock().UTC().Format(time.RFC3339)\n\tif _, err := s.db.Exec(`UPDATE experiment_arms SET ended_at = ?\n\t\tWHERE experiment_id = ? AND ended_at IS NULL`, now, experiment.id); err != nil {\n\t\treturn Experiment{}, fmt.Errorf(\"close open arm: %w\", err)\n\t}\n\tif _, err := s.db.Exec(`INSERT INTO experiment_arms (experiment_id, arm, started_at)\n\t\tVALUES (?, ?, ?)`, experiment.id, arm, now); err != nil {\n\t\treturn Experiment{}, fmt.Errorf(\"open arm %q: %w\", arm, err)\n\t}\n\treturn s.LoadExperiment(label)\n}\n\nfunc (s *Store) StopExperiment(label string) (Experiment, error) {\n\texperiment, err := s.LoadExperiment(label)\n\tif err != nil {\n\t\treturn Experiment{}, err\n\t}\n\tnow := learnOutcomeClock().UTC().Format(time.RFC3339)\n\tif _, err := s.db.Exec(`UPDATE experiment_arms SET ended_at = ?\n\t\tWHERE experiment_id = ? AND ended_at IS NULL`, now, experiment.id); err != nil {\n\t\treturn Experiment{}, fmt.Errorf(\"close open arm: %w\", err)\n\t}\n\tif _, err := s.db.Exec(`UPDATE experiments SET stopped_at = ? WHERE id = ?`, now, experiment.id); err != nil {\n\t\treturn Experiment{}, fmt.Errorf(\"stop experiment: %w\", err)\n\t}\n\treturn s.LoadExperiment(label)","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/internal/store/learn_experiment.go#L121-L157","documentation":"SwitchExperimentArm wraps the error from the INSERT that opens the new arm row after the previous arm was closed. This library throws it when starting the replacement arm fails, which can leave the experiment momentarily with no open arm (the close already succeeded).","triggerScenarios":"Calling Store.SwitchExperimentArm(label, arm) when 'INSERT INTO experiment_arms (experiment_id, arm, started_at)' fails: constraint violation, locked database, disk full.","commonSituations":"SQLITE_BUSY under concurrent writers; corrupted schema; disk quota exceeded mid-run.","solutions":["Read the wrapped cause with errors.Unwrap to identify the SQLite failure.","Retry the switch after resolving lock contention (busy_timeout).","Verify experiment_arms schema (experiment_id, arm, started_at, ended_at).","If the experiment is left armless, call SwitchExperimentArm again or StopExperiment to finalize cleanly."],"exampleFix":"// before\nif _, err := s.db.Exec(`INSERT INTO experiment_arms ...`, id, arm, now); err != nil {\n    return fmt.Errorf(\"open arm %q: %w\", arm, err)\n}\n// after\nif _, err := s.db.Exec(`INSERT INTO experiment_arms ...`, id, arm, now); err != nil {\n    return fmt.Errorf(\"open arm %q: %w\", arm, err)\n}\n// caller: retry on busy\nif errors.Is(err, sqlite.ErrBusy) { return store.SwitchExperimentArm(label, arm) }","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"exp, err := store.SwitchExperimentArm(label, arm)\nif err != nil {\n    // verify experiment state via LoadExperiment before retrying\n    return fmt.Errorf(\"switch arm: %w\", err)\n}","preventionTips":["Retry on SQLITE_BUSY with backoff","Keep experiment_arms schema up to date","Close other writers before switching"],"tags":["go","database","sqlite","experiment"],"backgroundTag":"database-write-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}