{"record":{"id":"f74f7c6a697f0190","repo":"gastownhall/beads","slug":"touch-dependency-coordination-parent-id-must-not","errorCode":null,"errorMessage":"touch dependency coordination: parent ID must not be empty","messagePattern":"touch dependency coordination: parent ID must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependency_coordination.go","lineNumber":35,"sourceCode":"\n// touchDependencyCoordinationInTx rewrites the coordination cells for both\n// dependency tables in a fixed order. Writers that need a stable view of an\n// issue's incoming parent-child edges use these cells to make concurrent Dolt\n// transactions conflict rather than cell-merge.\nfunc touchDependencyCoordinationInTx(ctx context.Context, tx DBTX, parentID string) error {\n\tfor _, tier := range [2]string{dependencyCoordinationDurableTier, dependencyCoordinationEphemeralTier} {\n\t\tif err := TouchDependencyCoordinationTableInTx(ctx, tx, parentID, tier); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// TouchDependencyCoordinationTableInTx rewrites the coordination cell for one\n// dependency table. table must be dependencies or wisp_dependencies.\nfunc TouchDependencyCoordinationTableInTx(ctx context.Context, tx DBTX, parentID, table string) error {\n\tif parentID == \"\" {\n\t\treturn fmt.Errorf(\"touch dependency coordination: parent ID must not be empty\")\n\t}\n\tif table != dependencyCoordinationDurableTier && table != dependencyCoordinationEphemeralTier {\n\t\treturn fmt.Errorf(\"touch dependency coordination: unsupported table %q\", table)\n\t}\n\tkey := dependencyCoordinationKey(parentID, table)\n\tif _, err := tx.ExecContext(ctx,\n\t\t\"REPLACE INTO local_metadata (`key`, value) VALUES (?, ?)\", key, strconv.FormatInt(FreshRowLock(), 10)); err != nil {\n\t\treturn fmt.Errorf(\"touch dependency coordination for %s: %w\", table, err)\n\t}\n\treturn nil\n}\n\nfunc dependencyCoordinationKey(parentID, table string) string {\n\tshard := dependencyCoordinationShard(parentID)\n\t// A tier has 4096 shard rows: enough to keep unrelated writes apart while\n\t// bounding the clone-local coordination state at 8192 rows. Same-parent\n\t// operations always resolve to the same shard; a hash collision only adds a\n\t// safe serialization conflict.","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependency_coordination.go#L17-L53","documentation":"TouchDependencyCoordinationTableInTx updates the freshness/coordination metadata cell for a dependency table and requires the parent issue ID to identify which shard/key to touch. An empty parent ID means the caller could not determine the owning issue, so the library refuses to write a meaningless coordination key. This is a defensive guard against callers bypassing required parameters.","triggerScenarios":"Calling TouchDependencyCoordinationTableInTx with parentID=\"\" — typically from a caller path (PersistDependenciesWithOptionsResult, addDependencyInTx, touchDependencyCoordinationInTx) where the parent issue ID was not resolved before the transaction ran.","commonSituations":"A bug in dependency persistence where an empty parent ID slips through earlier validation; hand-written code or tests invoking the internal touch helper without an ID; data corruption where the parent lookup returned empty.","solutions":["Fix the caller to resolve and pass a non-empty parent issue ID before opening the transaction.","Add earlier validation (reject empty parent ID at the API entry point) to fail fast.","If the parent ID should exist, check the input data — a missing/blank issue ID in the dependency record."],"exampleFix":"// before\nif err := TouchDependencyCoordinationTableInTx(ctx, tx, parentID, table); err != nil { ... }\n// after\nif parentID == \"\" {\n    return fmt.Errorf(\"cannot touch dependency coordination: empty parent ID\")\n}\nif err := TouchDependencyCoordinationTableInTx(ctx, tx, parentID, table); err != nil { ... }","handlingStrategy":"validation","validationCode":"if parentID == \"\" {\n    return fmt.Errorf(\"parent ID required before touching dependency coordination\")\n}\nerr := TouchDependencyCoordinationTableInTx(ctx, tx, parentID, table)","typeGuard":"func validParentID(id string) bool { return id != \"\" }","tryCatchPattern":"if err := TouchDependencyCoordinationTableInTx(ctx, tx, parentID, table); err != nil {\n    return fmt.Errorf(\"touch coordination (parent=%q): %w\", parentID, err)\n}","preventionTips":["Validate the parent issue ID at the API entry point, before opening the transaction.","Never construct coordination calls from values that may be zero-valued structs.","Add a unit test for empty-parent-ID rejection in your persistence path."],"tags":["storage","dependencies","coordination","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}