{"record":{"id":"c88f85ac419726b3","repo":"gastownhall/beads","slug":"create-marshal-waits-for-meta-w","errorCode":null,"errorMessage":"create: marshal waits-for meta: %w","messagePattern":"create: marshal waits-for meta: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/issue.go","lineNumber":1051,"sourceCode":"\t\t}\n\t\tif spec.SwapDirection {\n\t\t\tdep.IssueID, dep.DependsOnID = dep.DependsOnID, dep.IssueID\n\t\t}\n\t\tdepSourceIsWisp, err := u.isWispID(ctx, dep.IssueID)\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"create: determine dep source tier for %s: %w\", dep.IssueID, err)\n\t\t}\n\t\tif err := u.depRepo.Insert(ctx, dep, actor, DepInsertOpts{UseWispsTable: depSourceIsWisp}); err != nil {\n\t\t\treturn result, fmt.Errorf(\"create: add dep %s -> %s: %w\", dep.IssueID, dep.DependsOnID, err)\n\t\t}\n\t\tresult.PostCreateWrites = true\n\t}\n\n\tif params.WaitsFor != nil {\n\t\t// Spawner identity is the depends_on_id; metadata carries the gate.\n\t\tdep, err := types.NewWaitsForDependency(issue.ID, params.WaitsFor.SpawnerID, params.WaitsFor.Gate)\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"create: marshal waits-for meta: %w\", err)\n\t\t}\n\t\tif err := u.depRepo.Insert(ctx, dep, actor, DepInsertOpts{UseWispsTable: useWisp}); err != nil {\n\t\t\treturn result, fmt.Errorf(\"create: add waits-for: %w\", err)\n\t\t}\n\t\tresult.PostCreateWrites = true\n\t}\n\n\treturn result, nil\n}\n\nfunc validateExplicitIDPrefix(id, prefix, allowedPrefixes string) error {\n\tif strings.HasPrefix(id, prefix+\"-\") {\n\t\treturn nil\n\t}\n\tfor _, allowed := range strings.Split(allowedPrefixes, \",\") {\n\t\tallowed = strings.TrimSpace(allowed)\n\t\tif allowed != \"\" && strings.HasPrefix(id, allowed+\"-\") {\n\t\t\treturn nil","sourceCodeStart":1033,"sourceCodeEnd":1069,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/issue.go#L1033-L1069","documentation":"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.","triggerScenarios":"Calling Create with params.WaitsFor set where NewWaitsForDependency returns an error — typically an invalid/unserializable Gate value or a missing/invalid SpawnerID.","commonSituations":"Programmatically constructing WaitsFor with a gate object containing unsupported types (channels, funcs, non-JSON-serializable values); passing an empty spawner ID after a refactor.","solutions":["Inspect the wrapped error from NewWaitsForDependency for the exact marshal/validation cause","Ensure the Gate value is JSON-serializable metadata (strings, numbers, maps)","Provide a valid, non-empty SpawnerID","Build the dependency via types.NewWaitsForDependency yourself beforehand to fail fast before Create"],"exampleFix":"// before: non-serializable gate value\nparams.WaitsFor = &types.WaitsFor{SpawnerID: parent.ID, Gate: func() bool { return true }}\n// after: JSON-serializable gate metadata\nparams.WaitsFor = &types.WaitsFor{SpawnerID: parent.ID, Gate: map[string]any{\"status\": \"done\"}}","handlingStrategy":"validation","validationCode":"if params.WaitsFor != nil {\n    if params.WaitsFor.SpawnerID == \"\" {\n        return fmt.Errorf(\"waits-for requires a spawner ID\")\n    }\n    if _, err := json.Marshal(params.WaitsFor.Gate); err != nil {\n        return fmt.Errorf(\"gate not serializable: %w\", err)\n    }\n}","typeGuard":null,"tryCatchPattern":"_, err := uc.Create(ctx, params, actor)\nif err != nil && strings.Contains(err.Error(), \"marshal waits-for meta\") {\n    // rebuild WaitsFor with a serializable gate\n    params.WaitsFor.Gate = normalizeGate(params.WaitsFor.Gate)\n}","preventionTips":["Only use JSON-serializable values for Gate metadata","Call types.NewWaitsForDependency yourself first to fail fast","Never leave SpawnerID empty","Avoid storing functions/channels/time.Time without formatting in gates"],"tags":["storage","waits-for","serialization","go"],"backgroundTag":"metadata-marshal-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}