{"record":{"id":"be70560b940cebc0","repo":"gastownhall/beads","slug":"lookup-dependency-type-for-s-s-w","errorCode":null,"errorMessage":"lookup dependency type for %s -> %s: %w","messagePattern":"lookup dependency type for (.+?) -> (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependencies.go","lineNumber":938,"sourceCode":"func RemoveDependencyInTx(ctx context.Context, tx *sql.Tx, issueID, dependsOnID, actor string, emitEvent bool) (bool, error) {\n\treturn removeDependencyInTx(ctx, tx, issueID, dependsOnID, actor, emitEvent, nil)\n}\n\nfunc removeDependencyInTx(ctx context.Context, tx *sql.Tx, issueID, dependsOnID, actor string, emitEvent bool, recomputeResult *RecomputeIsBlockedResult) (bool, error) {\n\tisWisp := IsActiveWispInTx(ctx, tx, issueID)\n\t_, _, eventTable, depTable := WispTableRouting(isWisp)\n\n\t// Capture the row's type before deleting so we can dispatch the right\n\t// affected-set helper. If no row matches, treat as a no-op.\n\tvar depType, depMetadata string\n\trow := tx.QueryRowContext(ctx, fmt.Sprintf(\n\t\t`SELECT type, metadata FROM %s WHERE issue_id = ? AND %s = ?`, depTable, DepTargetExpr),\n\t\tissueID, dependsOnID)\n\tif err := row.Scan(&depType, &depMetadata); err != nil {\n\t\tif errors.Is(err, sql.ErrNoRows) {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, fmt.Errorf(\"lookup dependency type for %s -> %s: %w\", issueID, dependsOnID, err)\n\t}\n\n\tif _, err := tx.ExecContext(ctx, fmt.Sprintf(\n\t\t`DELETE FROM %s WHERE issue_id = ? AND %s = ?`, depTable, DepTargetExpr),\n\t\tissueID, dependsOnID); err != nil {\n\t\treturn false, fmt.Errorf(\"remove dependency: %w\", err)\n\t}\n\n\t// The lookup above returned early when no row matched, so reaching here means\n\t// an edge was actually deleted. Record the dependency_removed event on the\n\t// source issue's event table for bd CLI / library history observers — but only\n\t// when emitEvent is set, so structural removes stay silent (parity with the\n\t// proxied repo and with the symmetric AddDependencyInTx EmitEvent gate).\n\teventWritten := false\n\tif emitEvent {\n\t\tif err := RecordEventInTable(ctx, tx, eventTable, issueID, types.EventDependencyRemoved, actor,\n\t\t\tfmt.Sprintf(\"Removed dependency on %s\", dependsOnID)); err != nil {\n\t\t\treturn false, fmt.Errorf(\"record dependency_removed event: %w\", err)","sourceCodeStart":920,"sourceCodeEnd":956,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependencies.go#L920-L956","documentation":"This error wraps a non-ErrNoRows failure from the SELECT that fetches a dependency edge's type and metadata before deleting it in removeDependencyInTx. A missing edge is a normal no-op (returns false, nil); this error means the lookup query itself failed at the driver level.","triggerScenarios":"RemoveDependencyInTx / ApplyParentPatch calls removeDependencyInTx; the SELECT type, metadata FROM <depTable> WHERE issue_id=? AND <target>=? fails with a driver error other than sql.ErrNoRows — wrong depTable routing, connection loss, permissions, corrupted table.","commonSituations":"Removing a dependency while the DB is locked by another writer; the wisp-routing decision picked a table the user cannot read; Dolt server hiccup mid-transaction; schema drift removing type/metadata columns.","solutions":["Inspect the wrapped driver error; retry the removal transaction if transient.","Verify the dependency table still has type and metadata columns (run migrations).","Check the DB user's SELECT grants on both dependencies and wisp_dependencies.","Confirm the issue's wisp/issue status is current, since table routing depends on it."],"exampleFix":"// before: removal fails on locked database\nok, err := store.RemoveDependencyInTx(ctx, tx, \"bd-1\", \"bd-2\", actor, true)\n// after: wait for lock release / busy_timeout before retrying\ndb.Exec(\"PRAGMA busy_timeout=5000\")\nok, err = store.RemoveDependencyInTx(ctx, tx, \"bd-1\", \"bd-2\", actor, true)","handlingStrategy":"try-catch","validationCode":"// confirm the edge exists and its table is readable before removal\nisWisp := isActiveWisp(ctx, db, issueID)\ntable := \"dependencies\"; if isWisp { table = \"wisp_dependencies\" }\nvar t string\nerr := db.QueryRow(\"SELECT type FROM \"+table+\" WHERE issue_id=? AND COALESCE(depends_on_issue_id, depends_on_wisp_id, depends_on_external)=?\", issueID, dependsOnID).Scan(&t)\nif errors.Is(err, sql.ErrNoRows) { return nil } // nothing to remove\nif err != nil { return fmt.Errorf(\"precheck failed: %w\", err) }","typeGuard":"func isDepLookupFailure(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"lookup dependency type for\")\n}","tryCatchPattern":"removed, err := store.RemoveDependency(ctx, issueID, dependsOnID, actor)\nif isDepLookupFailure(err) && isTransient(errors.Unwrap(err)) {\n    removed, err = withBackoff(3, func() (bool, error) { return store.RemoveDependency(ctx, issueID, dependsOnID, actor) })\n}","preventionTips":["Treat no-op removals (missing edge) as success — the library already returns false, nil.","Keep the events/type/metadata columns migrated on both dependency tables.","Set busy_timeout to tolerate concurrent writers during dep removal.","Verify wisp status is fresh before removals; routing depends on it."],"tags":["storage","sql","dependencies","remove"],"backgroundTag":"dependency-remove-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}