gastownhall/beads · error

create: marshal waits-for meta: %w

Error message

create: marshal waits-for meta: %w

What it means

Thrown when types.NewWaitsForDependency fails while marshaling the waits-for gate metadata into a dependency during issue creation. Waits-for encodes a spawner ID and a gate condition into dependency metadata; marshaling fails if the gate metadata is invalid or cannot be serialized. It is a pre-insert validation failure, not a storage failure.

Source

Thrown at internal/storage/domain/issue.go:1051

		}
		if spec.SwapDirection {
			dep.IssueID, dep.DependsOnID = dep.DependsOnID, dep.IssueID
		}
		depSourceIsWisp, err := u.isWispID(ctx, dep.IssueID)
		if err != nil {
			return result, fmt.Errorf("create: determine dep source tier for %s: %w", dep.IssueID, err)
		}
		if err := u.depRepo.Insert(ctx, dep, actor, DepInsertOpts{UseWispsTable: depSourceIsWisp}); err != nil {
			return result, fmt.Errorf("create: add dep %s -> %s: %w", dep.IssueID, dep.DependsOnID, err)
		}
		result.PostCreateWrites = true
	}

	if params.WaitsFor != nil {
		// Spawner identity is the depends_on_id; metadata carries the gate.
		dep, err := types.NewWaitsForDependency(issue.ID, params.WaitsFor.SpawnerID, params.WaitsFor.Gate)
		if err != nil {
			return result, fmt.Errorf("create: marshal waits-for meta: %w", err)
		}
		if err := u.depRepo.Insert(ctx, dep, actor, DepInsertOpts{UseWispsTable: useWisp}); err != nil {
			return result, fmt.Errorf("create: add waits-for: %w", err)
		}
		result.PostCreateWrites = true
	}

	return result, nil
}

func validateExplicitIDPrefix(id, prefix, allowedPrefixes string) error {
	if strings.HasPrefix(id, prefix+"-") {
		return nil
	}
	for _, allowed := range strings.Split(allowedPrefixes, ",") {
		allowed = strings.TrimSpace(allowed)
		if allowed != "" && strings.HasPrefix(id, allowed+"-") {
			return nil

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped error from NewWaitsForDependency for the exact marshal/validation cause
  2. Ensure the Gate value is JSON-serializable metadata (strings, numbers, maps)
  3. Provide a valid, non-empty SpawnerID
  4. Build the dependency via types.NewWaitsForDependency yourself beforehand to fail fast before Create

Example fix

// before: non-serializable gate value
params.WaitsFor = &types.WaitsFor{SpawnerID: parent.ID, Gate: func() bool { return true }}
// after: JSON-serializable gate metadata
params.WaitsFor = &types.WaitsFor{SpawnerID: parent.ID, Gate: map[string]any{"status": "done"}}
Defensive patterns

Strategy: validation

Validate before calling

if params.WaitsFor != nil {
    if params.WaitsFor.SpawnerID == "" {
        return fmt.Errorf("waits-for requires a spawner ID")
    }
    if _, err := json.Marshal(params.WaitsFor.Gate); err != nil {
        return fmt.Errorf("gate not serializable: %w", err)
    }
}

Try / catch

_, err := uc.Create(ctx, params, actor)
if err != nil && strings.Contains(err.Error(), "marshal waits-for meta") {
    // rebuild WaitsFor with a serializable gate
    params.WaitsFor.Gate = normalizeGate(params.WaitsFor.Gate)
}

Prevention

When it happens

Trigger: Calling Create with params.WaitsFor set where NewWaitsForDependency returns an error — typically an invalid/unserializable Gate value or a missing/invalid SpawnerID.

Common situations: Programmatically constructing WaitsFor with a gate object containing unsupported types (channels, funcs, non-JSON-serializable values); passing an empty spawner ID after a refactor.

Related errors


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