{"record":{"id":"4a2108fc76b186b5","repo":"JuliusBrussee/caveman","slug":"trial-id-is-required","errorCode":null,"errorMessage":"trial_id is required","messagePattern":"trial_id is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/store/trial_store.go","lineNumber":61,"sourceCode":"func (s *Store) RecordPayload(label, requestID, traceID string, body []byte) {\n\ttrialID := strings.TrimPrefix(label, \"trial:\")\n\tif trialID == \"\" || trialID == label {\n\t\treturn\n\t}\n\t_, err := s.db.Exec(\n\t\t`INSERT OR IGNORE INTO trial_payloads\n\t\t  (trial_id, request_id, trace_id, ts, request_bytes, raw_request)\n\t\t  VALUES (?, ?, ?, ?, ?, ?)`,\n\t\ttrialID, requestID, traceID, time.Now().UTC().Format(time.RFC3339), len(body), append([]byte(nil), body...),\n\t)\n\tif err != nil && s.logger != nil {\n\t\ts.logger.Warn(\"local trial payload insert failed\", \"error\", err, \"request_id\", requestID)\n\t}\n}\n\nfunc (s *Store) StartTrial(trialID, agentSlug, command string) error {\n\tif trialID == \"\" {\n\t\treturn fmt.Errorf(\"trial_id is required\")\n\t}\n\t_, err := s.db.Exec(\n\t\t`INSERT INTO trial_runs (trial_id, agent_slug, command, started_at)\n\t\t  VALUES (?, ?, ?, ?)\n\t\t  ON CONFLICT(trial_id) DO UPDATE SET\n\t\t    agent_slug = excluded.agent_slug,\n\t\t    command = excluded.command`,\n\t\ttrialID, agentSlug, command, time.Now().UTC().Format(time.RFC3339),\n\t)\n\treturn err\n}\n\nfunc (s *Store) FinishTrial(trialID string, exitCode int) error {\n\tif trialID == \"\" {\n\t\treturn fmt.Errorf(\"trial_id is required\")\n\t}\n\t_, err := s.db.Exec(\n\t\t`UPDATE trial_runs SET ended_at = ?, exit_code = ? WHERE trial_id = ?`,","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/store/trial_store.go#L43-L79","documentation":"Store.StartTrial refuses to open a trial record when the trial identifier is the empty string. The check exists because trial_id is the primary key of the trial_runs table and every later operation (FinishTrial, AnalyzeTrial, payload joins) keys off it. An empty id would create an unusable or colliding row, so the store fails fast with a plain validation error before touching SQLite.","triggerScenarios":"Calling s.StartTrial(\"\", \"claude-code\", \"npm test\") — e.g. the caller generated the id from an env var or CLI flag that was unset, or passed a struct field that was never populated before the call.","commonSituations":"A trial-orchestration script reads CAVE_TRIAL_ID (or similar) from the environment and it is empty in CI; a wrapper derives the id conditionally and the branch fell through; refactoring renamed the field carrying the id and the zero value reaches the store.","solutions":["Generate a non-empty id before calling StartTrial, e.g. trialID := fmt.Sprintf(\"trial-%d\", time.Now().UnixNano()) or a UUID.","If the id comes from env/flags, validate it at the CLI boundary (fail with a clear message before the store call).","Check the error with errors.Is-style string comparison or just treat any StartTrial error as fatal for the trial and report it to the operator."],"exampleFix":"// before\ns.StartTrial(os.Getenv(\"CAVE_TRIAL_ID\"), slug, cmd)\n\n// after\ntrialID := os.Getenv(\"CAVE_TRIAL_ID\")\nif trialID == \"\" {\n    trialID = fmt.Sprintf(\"trial-%d\", time.Now().UnixNano())\n}\nif err := s.StartTrial(trialID, slug, cmd); err != nil {\n    log.Fatalf(\"start trial: %v\", err)\n}","handlingStrategy":"validation","validationCode":"func validTrialID(id string) bool { return strings.TrimSpace(id) != \"\" }\n\nif !validTrialID(trialID) {\n    return errors.New(\"trial id required: set CAVE_TRIAL_ID or pass -trial-id\")\n}\nerr := s.StartTrial(trialID, slug, cmd)","typeGuard":null,"tryCatchPattern":"if err := s.StartTrial(trialID, slug, cmd); err != nil {\n    if strings.Contains(err.Error(), \"trial_id is required\") {\n        // caller bug: fix id generation, do not retry\n        return fmt.Errorf(\"cannot start trial: %w\", err)\n    }\n    return err // database error\n}","preventionTips":["Generate the trial id in one place and pass the same variable to Start/Finish/Analyze.","Validate ids at the CLI/env boundary with a clear usage message.","Never derive the id independently at multiple call sites."],"tags":["go","sqlite","validation","trial-store"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}