{"record":{"id":"b2142f78c6f7d922","repo":"JuliusBrussee/caveman","slug":"open-first-arm-w","errorCode":null,"errorMessage":"open first arm: %w","messagePattern":"open first arm: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/store/learn_experiment.go","lineNumber":107,"sourceCode":"\tif label == \"\" || len(label) > 128 {\n\t\treturn Experiment{}, fmt.Errorf(\"experiment label must be 1-128 bytes\")\n\t}\n\tif len(note) > 4096 {\n\t\treturn Experiment{}, fmt.Errorf(\"note exceeds 4096 bytes\")\n\t}\n\tnow := learnOutcomeClock().UTC().Format(time.RFC3339)\n\tresult, err := s.db.Exec(`INSERT INTO experiments (label, sink_id, fix_kind, note, created_at)\n\t\tVALUES (?, ?, ?, ?, ?)`, label, sinkID, fixKind, note, now)\n\tif err != nil {\n\t\treturn Experiment{}, fmt.Errorf(\"start experiment %q: %w\", label, err)\n\t}\n\tid, err := result.LastInsertId()\n\tif err != nil {\n\t\treturn Experiment{}, fmt.Errorf(\"read experiment id: %w\", err)\n\t}\n\tif _, err := s.db.Exec(`INSERT INTO experiment_arms (experiment_id, arm, started_at)\n\t\tVALUES (?, ?, ?)`, id, experimentArmOn, now); err != nil {\n\t\treturn Experiment{}, fmt.Errorf(\"open first arm: %w\", err)\n\t}\n\treturn s.LoadExperiment(label)\n}\n\n// SwitchExperimentArm closes the open arm and opens the other one. Switching to\n// the arm already running is a no-op rather than an error: it keeps a repeated\n// call from fragmenting the interval history.\nfunc (s *Store) SwitchExperimentArm(label, arm string) (Experiment, error) {\n\tarm = strings.ToLower(strings.TrimSpace(arm))\n\tif arm != experimentArmOn && arm != experimentArmOff {\n\t\treturn Experiment{}, fmt.Errorf(\"arm must be %q or %q\", experimentArmOn, experimentArmOff)\n\t}\n\texperiment, err := s.LoadExperiment(label)\n\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)","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/internal/store/learn_experiment.go#L89-L125","documentation":"StartExperiment wraps the error from inserting the first experiment arm row (arm 'on') into the experiment_arms table. This library throws it when the SQLite INSERT fails right after the experiment row itself was created successfully. It leaves the experiment record present but without any open arm.","triggerScenarios":"Calling Store.StartExperiment when the INSERT INTO experiment_arms fails: locked database, disk full, schema missing/mismatched (experiment_arms table absent), or a NOT NULL/constraint violation on experiment_id, arm, or started_at.","commonSituations":"Running against a partially migrated or corrupted SQLite file; two processes writing concurrently and hitting SQLITE_BUSY; deploying a build whose schema setup did not run.","solutions":["Check the wrapped %w cause with errors.Unwrap to see the underlying SQLite error (e.g. 'no such table: experiment_arms' vs 'database is locked').","Run schema migrations to ensure experiment_arms exists with columns experiment_id, arm, started_at, ended_at.","Ensure no other process holds a long write transaction; set a busy_timeout on the SQLite connection.","Check free disk space and database file permissions, then retry StartExperiment."],"exampleFix":"// before\nexperiment, err := store.StartExperiment(label)\nif err != nil { return err }\n// after\nexperiment, err := store.StartExperiment(label)\nif err != nil {\n    log.Printf(\"start experiment %q failed: %v\", label, err)\n    return fmt.Errorf(\"starting experiment: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"var n int\n_ = s.db.QueryRow(`SELECT COUNT(*) FROM experiment_arms`).Scan(&n) // schema sanity check before calling","typeGuard":null,"tryCatchPattern":"experiment, err := store.StartExperiment(label)\nif err != nil {\n    return fmt.Errorf(\"start experiment: %w\", err) // inspect errors.Unwrap for sqlite cause\n}","preventionTips":["Run migrations before starting experiments","Set sqlite busy_timeout for concurrent writers","Monitor disk space on the DB volume"],"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"}