gastownhall/beads · warning

Failed to prepare %s: %v

Error message

Failed to prepare %s: %v

What it means

In doPull (internal/tracker/engine.go:494), after an external tracker issue is converted to a beads issue, the engine invokes the configurable PullHooks.AfterConvert hook to let the embedding tracker post-process/validate the conversion. If that hook returns an error, the issue is counted as skipped and this warning is printed with the external identifier. It is a per-issue, non-fatal warning issued by `Sync`/`doPull`.

Source

Thrown at internal/tracker/engine.go:494

				continue
			}
		}

		if existing != nil {
			// Conflict-aware pull: skip updating issues that were locally
			// modified since last sync. Conflict detection (Phase 2) will
			// handle these per the configured resolution strategy.
			// Without this guard, pull silently overwrites local changes
			// before conflict detection can compare timestamps.
			if lastSync != nil && existing.UpdatedAt.After(*lastSync) && !allowOverwriteIDs[existing.ID] && !prelinkedHydrateIDs[existing.ID] {
				stats.Skipped++
				continue
			}
		}

		if e.PullHooks != nil && e.PullHooks.AfterConvert != nil {
			if err := e.PullHooks.AfterConvert(ctx, &extIssue, conv, ref, existing, opts); err != nil {
				e.warn("Failed to prepare %s: %v", extIssue.Identifier, err)
				stats.Skipped++
				continue
			}
		}

		pendingDeps = appendFilteredDependencies(pendingDeps, conv.Dependencies, opts.DependencyTypes, opts.DependencySources)
		if opts.DryRun {
			dryRunIssue := *conv.Issue
			if strings.TrimSpace(ref) != "" {
				dryRunIssue.ExternalRef = strPtr(ref)
			}
			dryRunIssues = append(dryRunIssues, &dryRunIssue)
		}

		if existing != nil && pullIssueEqual(existing, conv.Issue, ref) {
			stats.Skipped++
			continue
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the `%v` detail in the warning — it comes from the specific tracker's AfterConvert hook and names the failing preparation step.
  2. Fix the tracker configuration the hook depends on (field mappings, label/epic lookups, credentials) and re-run the pull.
  3. Open or repair the offending remote issue so its data satisfies the hook's expectations, then re-pull.
  4. If the hook is custom/embedded, add error context in AfterConvert or return nil to skip preparation for issues it cannot handle.
  5. Use a dry-run pull first to identify all issues that would hit the hook failure.

Example fix

// before (embedded tracker hook)
func (t *Tracker) AfterConvert(ctx context.Context, ext *types.ExtIssue, conv *ConvertedIssue, ...) error {
    epicID := ext.Metadata["epic_id"].(string) // panics/wrong type -> error
}
// after
func (t *Tracker) AfterConvert(ctx context.Context, ext *types.ExtIssue, conv *ConvertedIssue, ...) error {
    epicID, _ := ext.Metadata["epic_id"].(string)
    if epicID == "" { return nil } // nothing to prepare
    ...
}
Defensive patterns

Strategy: validation

Validate before calling

// before pulling, dry-run to detect issues that would fail AfterConvert:
bd pull --dry-run   # issues skipped with 'Failed to prepare' show up without side effects
// for embedded trackers, ensure required remote fields exist before conversion:
if ext.Metadata["epic_id"] == nil { /* skip preparation path */ }

Type guard

func hasEpicMetadata(m map[string]any) (string, bool) {
    v, ok := m["epic_id"]
    if !ok { return "", false }
    s, ok := v.(string)
    return s, ok && s != ""
}

Try / catch

// per-issue warnings are not returned as errors; inspect them from the wrapper:
if err := eng.Sync(ctx, opts); err != nil {
    log.Printf("sync aborted: %v", err)
}
// grep stderr for 'Failed to prepare' and re-pull only those issues after fixing config

Prevention

When it happens

Trigger: Any `bd pull`/sync run against a tracker whose engine has PullHooks.AfterConvert set (e.g. the GitLab, Jira, or Linear adapters), when the hook's preparation step fails for a specific issue — custom field mapping errors, label/epic lookup failures, hook-level validation rejecting the converted issue, or context cancellation inside the hook.

Common situations: Tracker-specific config mistakes (missing custom-field or epic-link mapping); a remote issue has data the hook cannot normalize (unexpected state, missing required field); an embedded tracker whose hook needs extra API calls that fail due to rate limits or auth.

Related errors


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