{"record":{"id":"eb50f4a87041d1d4","repo":"gastownhall/beads","slug":"reading-last-pull-w","errorCode":null,"errorMessage":"reading last_pull: %w","messagePattern":"reading last_pull: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/linear/staleness.go","lineNumber":39,"sourceCode":"\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}\n\t\treturn time.Time{}, fmt.Errorf(\"reading last_pull: %w\", err)\n\t}\n\tts := strings.TrimSpace(string(data))\n\tif ts == \"\" {\n\t\treturn time.Time{}, nil\n\t}\n\tt, err := time.Parse(time.RFC3339, ts)\n\tif err != nil {\n\t\treturn time.Time{}, fmt.Errorf(\"parsing last_pull timestamp %q: %w\", ts, err)\n\t}\n\treturn t, nil\n}\n\n// IsPullStale returns true if the last pull is older than the given threshold,\n// or if no pull has ever been recorded.\nfunc IsPullStale(beadsDir string, threshold time.Duration) bool {\n\tlastPull, err := ReadLastPullTimestamp(beadsDir)\n\tif err != nil || lastPull.IsZero() {\n\t\treturn true","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/staleness.go#L21-L57","documentation":"ReadLastPullTimestamp tolerates a missing file (returns zero time, nil), but if os.ReadFile fails for any OTHER reason — permission denied, path is a directory, I/O error — it wraps the failure as \"reading last_pull: %w\" and returns it. Callers can then distinguish 'never pulled' from 'cannot read the timestamp file'.","triggerScenarios":"os.ReadFile(<beadsDir>/last_pull) fails with an error other than os.IsNotExist: the file exists but is unreadable (permission bits), last_pull is actually a directory, or a disk/IO error occurs.","commonSituations":"File permissions changed by another user or a security-hardening tool; a directory named last_pull created by mistake; running as a different user than the one that created .beads; read-only filesystem mount.","solutions":["Check and fix permissions on <beadsDir>/last_pull (should be 0600, owned by the running user)","If last_pull is a directory or corrupt, delete it — the next WriteLastPullTimestamp recreates it","Verify the process user matches the owner of the .beads directory","Check filesystem mount status (read-only remount, disk full)"],"exampleFix":"// repair a corrupt/unreadable last_pull\nif err := ReadLastPullTimestamp(beadsDir); err != nil {\n    os.Remove(filepath.Join(beadsDir, \"last_pull\"))\n    WriteLastPullTimestamp(beadsDir)\n}","handlingStrategy":"fallback","validationCode":"// pre-flight: file exists AND is a regular file\npath := filepath.Join(beadsDir, \"last_pull\")\nif st, err := os.Stat(path); err == nil && st.IsDir() {\n    os.Remove(path)\n}","typeGuard":"null","tryCatchPattern":"t, err := ReadLastPullTimestamp(beadsDir)\nif err != nil {\n    log.Printf(\"last_pull unreadable, treating as never pulled: %v\", err)\n    t = time.Time{}\n}","preventionTips":["Keep .beads owned by the same user that runs sync commands","Exclude .beads from permission-rewriting tools and security hardening","Monitor for unexpected files/directories named last_pull"],"tags":["filesystem","permissions","io"],"backgroundTag":"file-read-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}