{"record":{"id":"51638d0c6d3ca985","repo":"JuliusBrussee/caveman","slug":"arm-must-be-q-or-q","errorCode":null,"errorMessage":"arm must be %q or %q","messagePattern":"arm must be %q or %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/store/learn_experiment.go","lineNumber":118,"sourceCode":"\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)\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}","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/81536f57b3303b7de7f5bc5b564cc344f9112d68/proxy/internal/store/learn_experiment.go#L100-L136","documentation":"Input validation guard in SwitchExperimentArm: the requested arm string was not one of the two allowed values (on/off) after lowercasing and trimming. It fires whenever a caller passes a typo, mixed-case value, or any arm name other than the two sentinel constants.","triggerScenarios":"Thrown at proxy/internal/store/learn_experiment.go:118 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Pass exactly \"on\" or \"off\" as the arm argument","Normalize the arm string with strings.ToLower and strings.TrimSpace before calling","Check the CLI flag or config value that supplied the arm name for typos"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"81536f57b3303b7de7f5bc5b564cc344f9112d68","analyzedAt":"2026-08-27T02:25:50.681Z","contentChangedAt":"2026-08-27T02:25:50.681Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}