gastownhall/beads · warning

parsing last_pull timestamp %q: %w

Error message

parsing last_pull timestamp %q: %w

What it means

After reading .beads/last_pull, ReadLastPullTimestamp parses the trimmed contents as RFC 3339 (the format WriteLastPullTimestamp writes). If the contents are non-empty but not valid RFC 3339, it returns this wrapped parse error including the offending value %q. This indicates the timestamp file was corrupted or written by an incompatible tool.

Source

Thrown at internal/linear/staleness.go:47

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
	}
	return time.Since(lastPull) > threshold
}

// StalenessInfo holds computed staleness details for display purposes.
type StalenessInfo struct {
	LastPull    time.Time
	Age         time.Duration

View on GitHub (pinned to 71377f2769)

Solutions

  1. Delete the corrupt last_pull file — staleness logic treats 'no file' as 'never pulled' and WriteLastPullTimestamp will rewrite it after the next pull
  2. Check the beads version that wrote the file and upgrade/downgrade so formats match
  3. Manually rewrite the file with a valid RFC 3339 timestamp if you must preserve it
  4. Add a startup sanity check that rewrites last_pull when parsing fails

Example fix

// before: trust file blindly
t, err := ReadLastPullTimestamp(beadsDir)
// after: self-heal on parse error
t, err := ReadLastPullTimestamp(beadsDir)
if err != nil {
    os.Remove(filepath.Join(beadsDir, "last_pull"))
    t = time.Time{}
}
Defensive patterns

Strategy: fallback

Validate before calling

// validate before relying on the value
if data, err := os.ReadFile(filepath.Join(beadsDir, "last_pull")); err == nil {
    if _, perr := time.Parse(time.RFC3339, strings.TrimSpace(string(data))); perr != nil {
        os.Remove(filepath.Join(beadsDir, "last_pull"))
    }
}

Type guard

null

Try / catch

t, err := ReadLastPullTimestamp(beadsDir)
if err != nil {
    log.Printf("corrupt last_pull, resetting: %v", err)
    os.Remove(filepath.Join(beadsDir, "last_pull"))
    t = time.Time{}
}

Prevention

When it happens

Trigger: time.Parse(time.RFC3339, ts) fails on the file contents — e.g. the file holds a Unix epoch number, a locale-formatted date, partial/truncated text, or binary junk instead of an ISO 8601 string like 2026-08-30T12:00:00Z.

Common situations: An older beads version wrote a different timestamp format; the file was truncated by a crash or full disk; a user or script edited last_pull manually; another tool claimed the same file path.

Related errors


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