gastownhall/beads · warning

Failed to record push hash for %s: %v

Error message

Failed to record push hash for %s: %v

What it means

recordPushHash (internal/tracker/engine.go:920) is called by doPush after an issue is pushed, to store the pushed content hash via Store.SetLocalMetadata under pushHashKey(issue.ID). That hash is the sync's dirty-check baseline — on the next push, issues whose hash matches are considered unchanged and skipped. If the metadata write fails, the engine warns and returns; the push itself already succeeded, but the issue will look 'unpushed' next time.

Source

Thrown at internal/tracker/engine.go:920

	current := e.pushCacheValue(issue, externalRef)
	if current == "" {
		return false
	}
	stored, err := e.Store.GetLocalMetadata(ctx, e.pushHashKey(issue.ID))
	return err == nil && stored != "" && stored == current
}

// recordPushHash persists the current content-and-target fingerprint for issue
// so subsequent pushes can short-circuit via storedPushHashMatches. No-op when
// ContentHash is unset or returns "", or when the target is empty. Never called
// during dry-run.
func (e *Engine) recordPushHash(ctx context.Context, issue *types.Issue, externalRef string) {
	h := e.pushCacheValue(issue, externalRef)
	if h == "" {
		return
	}
	if err := e.Store.SetLocalMetadata(ctx, e.pushHashKey(issue.ID), h); err != nil {
		e.warn("Failed to record push hash for %s: %v", issue.ID, err)
	}
}

func (e *Engine) doPush(ctx context.Context, opts SyncOptions, skipIDs, forceIDs map[string]bool) (*PushStats, error) {
	ctx, span := syncTracer.Start(ctx, "tracker.push",
		trace.WithAttributes(
			attribute.String("sync.tracker", e.Tracker.DisplayName()),
			attribute.Bool("sync.dry_run", opts.DryRun),
		),
	)
	defer span.End()

	stats := &PushStats{}

	// BuildStateCache hook: pre-cache workflow states once before the loop.
	// Stored on Engine so tracker adapters can call ResolveState() during push.
	e.stateCache = nil
	if e.PushHooks != nil && e.PushHooks.BuildStateCache != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the `%v` detail; if it is a lock error, close other concurrent bd processes and re-run.
  2. Re-run `bd push` (or a no-op sync) — the hash write is retried for any issue whose stored hash is still missing.
  3. Check disk space and that the database file is writable.
  4. If the backend persistently fails metadata writes, run `bd doctor` to diagnose the storage layer.
  5. Worst case, force re-push (e.g. --force) so hashes are recomputed and recorded after storage is healthy.

Example fix

// before: parallel bd sync holds the write lock
Failed to record push hash for bd-123: database is locked (5)
// after: wait for the other process to exit, then
bd sync  # bd-123 hash recorded; subsequent pushes skip it as unchanged
Defensive patterns

Strategy: retry

Validate before calling

// before pushing, confirm storage is writable and uncontended:
bd doctor   # and ensure no concurrent bd processes are running

Try / catch

// recordPushHash only warns; detect degraded sync state and recover:
if strings.Contains(warnOutput, "Failed to record push hash") {
    time.Sleep(2 * time.Second)
    exec.Command("bd", "push").Run() // re-run so hashes are written; unchanged issues re-pushed once
}

Prevention

When it happens

Trigger: Any `bd push`/sync that completes an issue push and then fails the SetLocalMetadata write — DB write lock held by another bd process, storage closed/unavailable, disk full, or metadata-key constraint failures in the local-metadata table.

Common situations: Concurrent `bd` commands racing on the same database; read-only or full disk; the storage connection dropped mid-sync; embedding the engine with a storage backend that errors on local-metadata writes.

Related errors


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