gastownhall/beads · warning

Failed to update external_ref for %s: %v

Error message

Failed to update external_ref for %s: %v

What it means

During a non-batch push, bd created the issue in the external tracker successfully but then failed to write the resulting external_ref back to the local store via Store.UpdateIssue. The remote issue exists but the local record is not linked to it, so the next sync would attempt to create a duplicate. This is a warning-level message counted as a sync error; the issue is still counted as Created because the external side succeeded.

Source

Thrown at internal/tracker/engine.go:1094

			created, err := e.Tracker.CreateIssue(ctx, pushIssue)
			if err != nil {
				if isRateLimitExhausted(err) {
					return stats, fmt.Errorf("sync aborted: %w", err)
				}
				e.warn("Failed to create %s in %s: %v", issue.ID, e.Tracker.DisplayName(), err)
				stats.Errors++
				if isRateLimitedErr(err) {
					e.warnRateLimitAbort(err, len(issues)-stats.Created-stats.Updated-stats.Skipped-stats.Errors)
					return stats, nil
				}
				continue
			}

			// Update local issue with external ref
			ref := e.Tracker.BuildExternalRef(created)
			updates := map[string]interface{}{"external_ref": ref}
			if err := e.Store.UpdateIssue(ctx, issue.ID, updates, e.Actor); err != nil {
				e.warn("Failed to update external_ref for %s: %v", issue.ID, err)
				stats.Errors++
				// Note: issue WAS created externally, so we still count Created
				// but also flag the error so the user knows the link is broken
			}
			// Surface any partial-success warnings from the create (e.g. a
			// follow-up state change that failed) through the sync result so a
			// degraded push is visible rather than silently swallowed.
			for _, w := range created.Warnings {
				e.warn("%s (%s)", w, issue.ID)
			}
			// Remember what we just pushed so the next sync can skip the fetch.
			e.recordPushHash(ctx, issue, ref)
			stats.Created++
		} else if !opts.CreateOnly || forceIDs[issue.ID] {
			// Update existing external issue
			extID := e.Tracker.ExtractIdentifier(extRef)
			if extID == "" {
				stats.Skipped++

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the underlying storage error in the warning text (e.g. 'database is locked') and resolve the local DB issue (kill competing bd processes, wait for locks).
  2. Re-run `bd sync` or `bd push`; on the next pass the issue has no external_ref yet, so re-link or re-create will be attempted.
  3. Verify DB integrity with the storage backend's check tool (e.g. Dolt fsck / SQLite integrity_check) if errors persist.
  4. If duplicates appear, remove the duplicate external issue and fix the external_ref manually via `bd update`.
  5. Back up and repair the local store (.beads directory) if corruption is suspected.

Example fix

// before (shell): repeated lock errors during concurrent use
bd sync &
bd push &
// after: serialize write operations
bd sync
bd push
Defensive patterns

Strategy: retry

Validate before calling

// before sync: confirm the local store is writable and not locked
if _, err := os.Stat(".beads"); err != nil {
    log.Fatalf("no .beads store: %v", err)
}
out, _ := exec.Command("pgrep", "bd").Output()
if len(strings.TrimSpace(string(out))) > 0 { log.Println("another bd process may hold the write lock") }

Try / catch

// re-run sync when stats.Errors > 0 to relink missing external_refs
stats, err := engine.Sync(ctx, opts)
if err == nil && stats.Errors > 0 {
    time.Sleep(2 * time.Second)
    engine.Sync(ctx, opts)
}

Prevention

When it happens

Trigger: e.Store.UpdateIssue(ctx, issue.ID, {external_ref: ref}, e.Actor) returns an error right after a successful Tracker.CreateIssue — e.g. local DB lock, closed DB, storage driver failure, or a read-only database.

Common situations: Local database locked by another bd process during sync; disk-full or corrupted Dolt/SQLite store; running sync while a migration or compaction holds a write lock; stale database connection after a long-running push.

Related errors


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