{"record":{"id":"6fa187114fde7d4f","repo":"gastownhall/beads","slug":"beadsdir-must-not-be-empty","errorCode":null,"errorMessage":"beadsDir must not be empty","messagePattern":"beadsDir must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/linear/staleness.go","lineNumber":20,"sourceCode":"\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst (\n\tlastPullFileName      = \"last_pull\"\n\tDefaultStaleThreshold = 20 * time.Minute\n\tdebounceThreshold     = 5 * time.Minute\n)\n\n// WriteLastPullTimestamp writes the current time as ISO 8601 to .beads/last_pull.\nfunc WriteLastPullTimestamp(beadsDir string) error {\n\tif beadsDir == \"\" {\n\t\treturn fmt.Errorf(\"beadsDir must not be empty\")\n\t}\n\tpath := filepath.Join(beadsDir, lastPullFileName)\n\tts := time.Now().UTC().Format(time.RFC3339)\n\treturn os.WriteFile(path, []byte(ts+\"\\n\"), 0600)\n}\n\n// ReadLastPullTimestamp reads the last pull timestamp from .beads/last_pull.\n// Returns the zero time if the file doesn't exist or is unreadable.\nfunc ReadLastPullTimestamp(beadsDir string) (time.Time, error) {\n\tif beadsDir == \"\" {\n\t\treturn time.Time{}, fmt.Errorf(\"beadsDir must not be empty\")\n\t}\n\tpath := filepath.Join(beadsDir, lastPullFileName)\n\tdata, err := os.ReadFile(path) // #nosec G304 -- path is constrained to the beads directory.\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn time.Time{}, nil\n\t\t}","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/staleness.go#L2-L38","documentation":"WriteLastPullTimestamp records the current UTC time (ISO 8601) into <beadsDir>/.beads/last_pull to support pull staleness checks. It validates its input first: if beadsDir is an empty string it returns this sentinel error instead of attempting to write a file at a meaningless path. This is a programmer-error guard, not a runtime condition.","triggerScenarios":"Calling WriteLastPullTimestamp(\"\") — typically because the caller derived the beads directory from an unset config value, empty flag, or failed path resolution before writing the timestamp after a pull.","commonSituations":"A --beads-dir flag left empty and not defaulted; an environment variable (e.g. BEADS_DIR) unset; a config struct field not populated because initialization was skipped in tests or a new code path.","solutions":["Ensure the beads directory is resolved before calling (default to ./.beads or the project root's .beads)","Check the flag/env/config value feeding beadsDir for emptiness at startup","Return or surface the configuration error earlier instead of passing an empty string down","In tests, pass t.TempDir() rather than \"\""],"exampleFix":"// before\nWriteLastPullTimestamp(cfg.BeadsDir) // cfg.BeadsDir may be \"\"\n// after\nif cfg.BeadsDir == \"\" {\n    return fmt.Errorf(\"beads dir not configured\")\n}\nWriteLastPullTimestamp(cfg.BeadsDir)","handlingStrategy":"validation","validationCode":"if beadsDir == \"\" {\n    return fmt.Errorf(\"beads dir is not configured\")\n}\n// safe to call\nerr := WriteLastPullTimestamp(beadsDir)","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Default the beads directory at config load time (e.g. ./.beads) instead of allowing empty","Validate configuration once at startup, not at each call site","Use t.TempDir() in tests instead of empty strings"],"tags":["validation","configuration","filesystem"],"backgroundTag":"missing-configuration-value","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}