{"record":{"id":"9d8f083a44377041","repo":"JuliusBrussee/caveman","slug":"history-requires-memory-id","errorCode":null,"errorMessage":"history requires memory id","messagePattern":"history requires memory id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mem/store.go","lineNumber":279,"sourceCode":"\t\tText:       newText,\n\t\tCreatedAt:  now,\n\t\tValidFrom:  now,\n\t\tSupersedes: oldID,\n\t}, nil\n}\n\nfunc validateMemorySize(text string) error {\n\tif len(text) > MaxMemoryBytes {\n\t\treturn fmt.Errorf(\"%w: memory is %d bytes, over the %d-byte cap\", ErrMemoryTooLarge, len(text), MaxMemoryBytes)\n\t}\n\treturn nil\n}\n\n// History returns the complete oldest-to-newest supersession chain containing\n// id. Broken/cyclic lineage is rejected rather than returning partial history.\nfunc (s *Store) History(id string) ([]Memory, error) {\n\tif strings.TrimSpace(id) == \"\" {\n\t\treturn nil, fmt.Errorf(\"history requires memory id\")\n\t}\n\tcurrent, err := s.memoryByID(id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tseen := map[string]bool{current.ID: true}\n\tvar before []Memory\n\tcursor := current\n\tfor cursor.Supersedes != \"\" {\n\t\tprev, err := s.memoryByID(cursor.Supersedes)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"broken supersession history at %s: %w\", cursor.ID, err)\n\t\t}\n\t\tif seen[prev.ID] {\n\t\t\treturn nil, fmt.Errorf(\"cyclic supersession history at %s\", prev.ID)\n\t\t}\n\t\tseen[prev.ID] = true\n\t\tbefore = append(before, prev)","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/mem/store.go#L261-L297","documentation":"History(id) rejects an empty or whitespace-only memory id before touching the database. It is a plain precondition check: the history walk needs a concrete starting memory, and an blank id would otherwise surface as a confusing 'not found' or full-table scan downstream.","triggerScenarios":"Calling Store.History(\"\") or History(\"   \") — typically an uninitialized variable, an empty id field parsed from a struct/flag, or a caller passing a pointer that was never populated.","commonSituations":"Reading ids from optional JSON/YAML fields and forwarding them without checking presence; refactoring that drops the assignment to the id variable; CLI wrappers forwarding a missing --id flag value.","solutions":["Check the caller: find where the id originates and ensure it is set before calling History","Guard with strings.TrimSpace(id) != \"\" at the API boundary (HTTP handler, CLI flag parsing) and return a proper 'missing id' error to your own caller","Default or reject early: if the id is genuinely optional in your flow, decide an explicit behavior (latest memory, or error) instead of forwarding an empty string"],"exampleFix":"// before\nhist, err := store.History(memID) // memID accidentally \"\"\n\n// after\nif strings.TrimSpace(memID) == \"\" {\n    return fmt.Errorf(\"memory id is required\")\n}\nhist, err := store.History(memID)","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(id) == \"\" {\n    return errors.New(\"memory id required\")\n}\nhist, err := store.History(id)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate required string params at your own API edge before calling History","When ids come from parsed config/JSON, check presence at parse time","Fail fast on empty ids instead of forwarding them deeper"],"tags":["go","validation","api-misuse","memory-store"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}