{"record":{"id":"4299df216b55d401","repo":"gastownhall/beads","slug":"parsing-waits-for-metadata-to-set-also-blocks-w","errorCode":null,"errorMessage":"parsing waits-for metadata to set also_blocks: %w","messagePattern":"parsing waits-for metadata to set also_blocks: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/types/types.go","lineNumber":1429,"sourceCode":"\treturn NewGraphEdgeDependency(issueID, spawnerID, DepWaitsFor, gate, \"\", \"\", \"\", nil)\n}\n\n// NewWaitsForBlockingDependency builds a waits-for dependency that also\n// carries classic blocking semantics (GH#3783): set also_blocks in the\n// metadata so waitsForGateBlockedSQL additionally blocks while the spawner\n// itself is open, not only while it has an open parent-child child. Use this\n// instead of NewWaitsForDependency exactly when the caller is collapsing a\n// would-be DepBlocks edge (from needs/depends_on) into this waits-for edge\n// because the two would otherwise collide on the same (source, target) pair\n// — never for a plain waits_for with no matching needs/depends_on entry.\nfunc NewWaitsForBlockingDependency(issueID, spawnerID, gate string) (*Dependency, error) {\n\tdep, err := NewWaitsForDependency(issueID, spawnerID, gate)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar meta WaitsForMeta\n\tif err := json.Unmarshal([]byte(dep.Metadata), &meta); err != nil {\n\t\treturn nil, fmt.Errorf(\"parsing waits-for metadata to set also_blocks: %w\", err)\n\t}\n\tmeta.AlsoBlocks = true\n\traw, err := json.Marshal(meta)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"serializing waits-for also_blocks metadata: %w\", err)\n\t}\n\tdep.Metadata = string(raw)\n\treturn dep, nil\n}\n\n// NewGraphNodeDependency builds the dependency record for a graph-plan node's\n// inline dep, shared by the embedded and domain apply paths so their\n// resolution semantics cannot drift: an empty type defaults to blocks, and\n// the target resolves as a plan-local key first, then as a literal issue ID.\n// Waits-for deps carry gate metadata like waits-for edges (all-children\n// default, no explicit spawner).\nfunc NewGraphNodeDependency(issueID string, depType DependencyType, target string, keyToID map[string]string) (*Dependency, error) {\n\tif depType == \"\" {","sourceCodeStart":1411,"sourceCodeEnd":1447,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/types/types.go#L1411-L1447","documentation":"NewWaitsForBlockingDependency sets also_blocks=true by round-tripping the dependency's metadata through json.Unmarshal. If the metadata produced by NewWaitsForDependency is not valid JSON, unmarshaling into WaitsForMeta fails with this wrapper.","triggerScenarios":"json.Unmarshal([]byte(dep.Metadata), &meta) failing inside NewWaitsForBlockingDependency, i.e. dep.Metadata holds invalid or non-JSON content.","commonSituations":"Only possible if the underlying NewGraphEdgeDependency changed what it stores in metadata (e.g. a version where waits-for rows carry '{}' or plain text), or a fork/patch altered metadata format.","solutions":["Rebuild against the current beads version so NewWaitsForDependency emits valid JSON metadata","Verify dep.Metadata before calling by json.Valid([]byte(dep.Metadata))","If working with pre-migration-0059 rows that store '{}' or empty metadata, migrate them first"],"exampleFix":"// before\ndep, _ := NewWaitsForBlockingDependency(issueID, spawnerID, gate) // fails on legacy metadata\n// after\n// check first\nif !json.Valid([]byte(dep.Metadata)) { /* repair metadata or recreate the dependency */ }","handlingStrategy":"validation","validationCode":"func metadataIsJSON(s string) bool { return json.Valid([]byte(s)) }\n// call after building the base dependency:\n// if !metadataIsJSON(dep.Metadata) { /* repair or recreate before NewWaitsForBlockingDependency */ }","typeGuard":"func validWaitsForMeta(raw string) bool {\n\tvar m types.WaitsForMeta\n\treturn json.Unmarshal([]byte(raw), &m) == nil\n}","tryCatchPattern":"dep, err := types.NewWaitsForBlockingDependency(issueID, spawnerID, gate)\nif err != nil {\n\tvar perr *json.SyntaxError\n\tif errors.As(err, &perr) {\n\t\t// metadata not parseable: recreate the dependency from scratch\n\t}\n\treturn err\n}","preventionTips":["Never hand-edit dependency metadata columns directly","Run pending migrations before reading/writing waits-for rows","Round-trip metadata through WaitsForMeta when modifying it"],"tags":["go","json","unmarshal","waits-for","metadata"],"backgroundTag":"json-unmarshal-failure","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}