gastownhall/beads · error
%w: %w
Error message
%w: %w
What it means
ClassifyPublicCreateError maps low-level persistence errors from a create batch onto typed public errors. A SQLState 23505 (unique violation) is re-wrapped as storage.ErrAlreadyExists together with the underlying error, so callers can detect duplicates with errors.Is while retaining the root cause.
Source
Thrown at internal/storage/issueops/public_create.go:100
prepared.WaitsFor.Gate = string(types.WaitsForAllChildren)
}
if err := ValidatePublicCreateRequest(prepared); err != nil {
return publicops.CreateRequest{}, err
}
return prepared, nil
}
// ClassifyPublicCreateError adds ErrValidation only to known deterministic
// public-create failures and leaves infrastructure and commit errors intact.
func ClassifyPublicCreateError(err error) error {
if err == nil || errors.Is(err, storage.ErrValidation) || errors.Is(err, storage.ErrAlreadyExists) {
return err
}
var conflict *domain.DependencyTypeConflictError
var hierarchyConflict *domain.DependencyHierarchyConflictError
var stateErr interface{ SQLState() string }
if errors.As(err, &stateErr) && stateErr.SQLState() == "23505" {
return fmt.Errorf("%w: %w", storage.ErrAlreadyExists, err)
}
if errors.Is(err, storage.ErrPrefixMismatch) || errors.Is(err, domain.ErrSelfDependency) || errors.Is(err, types.ErrFieldTooLong) || errors.Is(err, domain.ErrDependencyCycle) || errors.As(err, &conflict) || errors.As(err, &hierarchyConflict) {
return publicCreateValidationError(err)
}
// A create whose requested relationship names a row that does not exist is
// refused by the dependency write: as the typed endpoint refusal where the
// write could name the absent endpoint, and as the target foreign key where
// it could not. The caller asked for an edge to something absent, so this
// is a deterministic refusal rather than an infrastructure error: classify
// it the same way ExecuteCreate refuses a skipped dependency, so every
// backend reports a missing dependency, parent, or waits-for target as
// ErrValidation wrapping ErrNotFound.
var missingEndpoint *domain.DependencyEndpointNotFoundError
if errors.As(err, &missingEndpoint) || dberrors.IsMissingForeignKeyTarget(err) {
return publicCreateValidationError(fmt.Errorf("create: dependency target does not exist: %w: %w", err, storage.ErrNotFound))
}
return err
}View on GitHub (pinned to 71377f2769)
Solutions
- Handle the duplicate: check errors.Is(err, storage.ErrAlreadyExists) and treat the create as already-done if the existing row matches
- Make batch creates idempotent — deduplicate IDs/identities in the batch before calling ExecuteCreateBatch
- Unwrap the inner error (%w: %w) to see which constraint/index was violated and adjust the payload
Example fix
// before
_, err := ExecuteCreate(ctx, tx, req)
if err != nil { return err }
// after
_, err := ExecuteCreate(ctx, tx, req)
if err != nil {
if errors.Is(err, storage.ErrAlreadyExists) {
return nil // already created; idempotent no-op
}
return err
} Defensive patterns
Strategy: try-catch
Validate before calling
seen := map[string]bool{}
for _, r := range batch {
if seen[r.Issue.ID] { return errors.New("duplicate id in batch: " + r.Issue.ID) }
seen[r.Issue.ID] = true
} Try / catch
_, err := ExecuteCreateBatch(ctx, tx, batch)
if err != nil {
if errors.Is(err, storage.ErrAlreadyExists) {
// duplicate: log and continue idempotently
return nil
}
return err
} Prevention
- Deduplicate issue IDs/identities before batch creation
- Make create flows idempotent: treat ErrAlreadyExists as success when content matches
- On retry after partial failure, check existence first or upsert where supported
When it happens
Trigger: Calling ExecuteCreate/ExecuteCreateBatch when the insert violates a unique constraint — most often creating an issue whose ID already exists, or a duplicate row in a batch with identical deterministic identities.
Common situations: Re-running a batch import without idempotency handling; two issues in one batch that normalize to the same identity; retry after a partial failure where the first attempt already committed.
Related errors
- canonical issue not found: %s
- failed to add duplicate link: %w
- failed to close duplicate: %w
- applyGraph: node %q: %w
- %w: %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/5f35b678c74d875c.
Report an issue: GitHub.