gastownhall/beads · error
provenance: unknown kind %q
Error message
provenance: unknown kind %q
What it means
ValidateProvenanceEvent checks ev.Kind against knownProvKinds, the closed set of valid provenance kinds (cut, claim, suspend, resume, handoff, commit, land, used). An unrecognized Kind is rejected with 'provenance: unknown kind %q'. This keeps the provenance log to a known vocabulary that downstream tooling can interpret.
Source
Thrown at internal/storage/issueops/provenance.go:55
}
// ReservedProvSource is reserved for derived/reconstructed events so a consumer's
// read-first honesty filter can exclude backfilled rows. The record path rejects
// it (case-insensitively): real producers must name their own source.
const ReservedProvSource = "ingest-backfill"
var gitSHARE = regexp.MustCompile(`^[0-9a-f]{40}$`)
// ValidateProvenanceEvent checks the structural fields of a provenance event
// before it is recorded: kind, ref_kind (when present), the git-sha ref shape,
// and the reserved source. It never interprets the opaque actor/ref values. It
// is exported so the CLI can fail early with the same rules the store enforces.
func ValidateProvenanceEvent(ev types.ProvenanceEvent) error {
if strings.TrimSpace(ev.IssueID) == "" {
return fmt.Errorf("provenance: issue id is required")
}
if _, ok := knownProvKinds[ev.Kind]; !ok {
return fmt.Errorf("provenance: unknown kind %q", ev.Kind)
}
if strings.TrimSpace(ev.Source) == "" {
return fmt.Errorf("provenance: source is required")
}
if strings.EqualFold(strings.TrimSpace(ev.Source), ReservedProvSource) {
return fmt.Errorf("provenance: source %q is reserved for ingest backfill and cannot be recorded directly", ReservedProvSource)
}
if ev.RefKind != nil {
if _, ok := knownProvRefKinds[*ev.RefKind]; !ok {
return fmt.Errorf("provenance: unknown ref-kind %q", *ev.RefKind)
}
if ev.Ref == nil || *ev.Ref == "" {
return fmt.Errorf("provenance: ref-kind %q requires a ref", *ev.RefKind)
}
if *ev.RefKind == "git-sha" {
if !gitSHARE.MatchString(*ev.Ref) {
return fmt.Errorf("provenance: ref-kind git-sha requires a 40-character lowercase hex ref")
}View on GitHub (pinned to 71377f2769)
Solutions
- Use the exported types.Prov* constants instead of hand-written strings for ev.Kind
- Check the accepted set (see knownProvKinds in internal/storage/issueops/provenance.go) and map your event to the nearest supported kind
- If a new kind is genuinely needed, propose it upstream rather than bypassing validation
- Pre-validate with ValidateProvenanceEvent before opening a transaction so failures are cheap
Example fix
// before
ev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvKind("deployed"), Source: "ci"}
// after
ev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvLand, Source: "ci"} // use a known Prov* constant Defensive patterns
Strategy: validation
Validate before calling
validKinds := map[types.ProvKind]bool{
types.ProvCut: true, types.ProvClaim: true, types.ProvSuspend: true, types.ProvResume: true,
types.ProvHandoff: true, types.ProvCommit: true, types.ProvLand: true, types.ProvUsed: true,
}
if !validKinds[ev.Kind] { return fmt.Errorf("unsupported provenance kind %q", ev.Kind) } Type guard
func isKnownProvKind(k types.ProvKind) bool {
switch k {
case types.ProvCut, types.ProvClaim, types.ProvSuspend, types.ProvResume,
types.ProvHandoff, types.ProvCommit, types.ProvLand, types.ProvUsed:
return true
}
return false
} Try / catch
if err := issueops.ValidateProvenanceEvent(ev); err != nil {
var kind types.ProvKind
if strings.Contains(err.Error(), "unknown kind") {
_ = kind // map or drop the event; do not pass it to the store
return fmt.Errorf("unsupported provenance kind %q", ev.Kind)
}
return err
} Prevention
- Always use the exported types.Prov* constants; never cast raw strings to ProvKind
- Map external event vocabularies to the supported kinds at the boundary
- Keep a mapping table for tool-specific events (e.g. deploy -> ProvLand)
- Re-check kinds after beads upgrades in case the closed set changes
When it happens
Trigger: Recording an event whose ev.Kind is not one of types.ProvCut/ProvClaim/ProvSuspend/ProvResume/ProvHandoff/ProvCommit/ProvLand/ProvUsed — e.g. the zero value of ProvKind, a misspelled kind, or a kind string invented by an external tool.
Common situations: Constructing types.ProvenanceEvent with a raw string cast instead of the typed constants; upgrading beads and encountering kinds from newer/older schemas; automation emitting ad-hoc event kinds.
Related errors
- provenance: unknown ref-kind %q
- ExternalDoltConfig: TLSCert set without TLSKey
- unknown backend %q (want one of: %s)
- %w: invalid persistence mode %q
- %w: invalid issue type %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e9ce057d1fe1835d.
Report an issue: GitHub.