gastownhall/beads · warning

reading last_pull: %w

Error message

reading last_pull: %w

What it means

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'.

Source

Thrown at internal/linear/staleness.go:39

	}
	path := filepath.Join(beadsDir, lastPullFileName)
	ts := time.Now().UTC().Format(time.RFC3339)
	return os.WriteFile(path, []byte(ts+"\n"), 0600)
}

// ReadLastPullTimestamp reads the last pull timestamp from .beads/last_pull.
// Returns the zero time if the file doesn't exist or is unreadable.
func ReadLastPullTimestamp(beadsDir string) (time.Time, error) {
	if beadsDir == "" {
		return time.Time{}, fmt.Errorf("beadsDir must not be empty")
	}
	path := filepath.Join(beadsDir, lastPullFileName)
	data, err := os.ReadFile(path) // #nosec G304 -- path is constrained to the beads directory.
	if err != nil {
		if os.IsNotExist(err) {
			return time.Time{}, nil
		}
		return time.Time{}, fmt.Errorf("reading last_pull: %w", err)
	}
	ts := strings.TrimSpace(string(data))
	if ts == "" {
		return time.Time{}, nil
	}
	t, err := time.Parse(time.RFC3339, ts)
	if err != nil {
		return time.Time{}, fmt.Errorf("parsing last_pull timestamp %q: %w", ts, err)
	}
	return t, nil
}

// IsPullStale returns true if the last pull is older than the given threshold,
// or if no pull has ever been recorded.
func IsPullStale(beadsDir string, threshold time.Duration) bool {
	lastPull, err := ReadLastPullTimestamp(beadsDir)
	if err != nil || lastPull.IsZero() {
		return true

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check and fix permissions on <beadsDir>/last_pull (should be 0600, owned by the running user)
  2. If last_pull is a directory or corrupt, delete it — the next WriteLastPullTimestamp recreates it
  3. Verify the process user matches the owner of the .beads directory
  4. Check filesystem mount status (read-only remount, disk full)

Example fix

// repair a corrupt/unreadable last_pull
if err := ReadLastPullTimestamp(beadsDir); err != nil {
    os.Remove(filepath.Join(beadsDir, "last_pull"))
    WriteLastPullTimestamp(beadsDir)
}
Defensive patterns

Strategy: fallback

Validate before calling

// pre-flight: file exists AND is a regular file
path := filepath.Join(beadsDir, "last_pull")
if st, err := os.Stat(path); err == nil && st.IsDir() {
    os.Remove(path)
}

Type guard

null

Try / catch

t, err := ReadLastPullTimestamp(beadsDir)
if err != nil {
    log.Printf("last_pull unreadable, treating as never pulled: %v", err)
    t = time.Time{}
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/eb50f4a87041d1d4. Report an issue: GitHub.