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
- Read the `%v` detail in the warning — it comes from the specific tracker's AfterConvert hook and names the failing preparation step.
- Fix the tracker configuration the hook depends on (field mappings, label/epic lookups, credentials) and re-run the pull.
- Open or repair the offending remote issue so its data satisfies the hook's expectations, then re-pull.
- If the hook is custom/embedded, add error context in AfterConvert or return nil to skip preparation for issues it cannot handle.
- 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
- Validate tracker field mappings/config before large pulls.
- Use --dry-run pulls after changing tracker configuration.
- Keep AfterConvert hooks defensive: tolerate missing/odd remote fields.
- Fix malformed remote issues at the source tracker.
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
- pull from %s/%s reported success but merged nothing into %s:
- pull from %s: %w
- httpapi: a configured role fires this workspace's hooks; thi
- httpapi: the configured provider fires this workspace's hook
- database not available: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/eb26486044860e9b.
Report an issue: GitHub.