{"record":{"id":"4faf4266125ea3dc","repo":"gastownhall/beads","slug":"check-retarget-collision-in-s-w","errorCode":null,"errorMessage":"check retarget collision in %s: %w","messagePattern":"check retarget collision in (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependencies.go","lineNumber":864,"sourceCode":"\n\tquery := fmt.Sprintf(`\n\t\tSELECT 1 FROM %s moving\n\t\tJOIN %s existing ON moving.issue_id = existing.issue_id\n\t\tWHERE moving.%s = ?\n\t\t  AND (existing.%s = ? OR existing.%s = ?)\n\t\tLIMIT 1\n\t`, table, table, sourceCol, conflictCols[0], conflictCols[1])\n\n\tvar found int\n\terr := tx.QueryRowContext(ctx, query, id, id, id).Scan(&found)\n\tif err == sql.ErrNoRows {\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\tif isTableNotExistError(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"check retarget collision in %s: %w\", table, err)\n\t}\n\treturn fmt.Errorf(\"retarget to %s collides with existing dependency target in %s\", id, table)\n}\n\n//nolint:gosec // G201: table and typedCol are hardcoded constants.\nfunc checkRenameTargetCollision(ctx context.Context, tx DBTX, table, typedCol, newID string) error {\n\tvar otherCols []string\n\tswitch typedCol {\n\tcase \"depends_on_issue_id\":\n\t\totherCols = []string{\"depends_on_wisp_id\", \"depends_on_external\"}\n\tcase \"depends_on_wisp_id\":\n\t\totherCols = []string{\"depends_on_issue_id\", \"depends_on_external\"}\n\tdefault:\n\t\treturn fmt.Errorf(\"checkRenameTargetCollision: unsupported typed column %q\", typedCol)\n\t}\n\n\tquery := fmt.Sprintf(`\n\t\tSELECT 1 FROM %s a","sourceCodeStart":846,"sourceCodeEnd":882,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependencies.go#L846-L882","documentation":"This error is the wrapped-SQL-failure variant from checkRetargetTargetCollision: the collision probe query itself failed at the database level while preparing a wisp<->issue retarget. It is distinct from the deliberate collision error (3543) — here the query errored (driver, syntax, connectivity) rather than finding a conflicting row. Table-not-exist is deliberately tolerated and returns nil.","triggerScenarios":"RetargetInboundDependenciesToWispInTx or RetargetInboundDependenciesToIssueInTx runs its JOIN probe (SELECT 1 FROM <table> moving JOIN <table> existing ...) and the driver returns an error other than sql.ErrNoRows or a table-not-exist condition — e.g. corrupted schema, connection reset, query timeout.","commonSituations":"Database connection dropped between transaction start and the probe; Dolt server restarted mid-operation; malformed/partially migrated dependency tables; permissions revoking SELECT on dependencies tables.","solutions":["Check the wrapped driver error; treat connection/timeout errors as transient and retry the whole move transaction.","Verify the database file/server is healthy and reachable.","Confirm both dependencies and wisp_dependencies exist with the expected typed target columns (run migrations).","Check DB user grants allow SELECT on the dependency tables."],"exampleFix":"// before: transient connection loss aborts the move\nerr := MoveIssuePersistenceInTx(ctx, tx, id)\n// after: retry the whole transaction on transient errors\nif err := MoveIssuePersistenceInTx(ctx, tx, id); isTransientDBError(err) {\n    err = retryTx(func(tx *sql.Tx) error { return MoveIssuePersistenceInTx(ctx, tx, id) })\n}","handlingStrategy":"retry","validationCode":"// probe the collision-check tables are readable before starting the move\nfor _, t := range []string{\"dependencies\", \"wisp_dependencies\"} {\n    if _, err := db.Query(fmt.Sprintf(\"SELECT 1 FROM %s LIMIT 1\", t)); err != nil {\n        return fmt.Errorf(\"cannot read %s: %w\", t, err)\n    }\n}","typeGuard":"func isCollisionProbeFailure(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"check retarget collision\")\n}","tryCatchPattern":"err := store.MoveIssue(ctx, id)\nif isCollisionProbeFailure(err) && isTransient(errors.Unwrap(err)) {\n    err = withBackoff(3, func() error { return store.MoveIssue(ctx, id) })\n}","preventionTips":["Use connection pooling with health checks so stale connections fail fast before transactions.","Set explicit query timeouts so probe queries fail cleanly and can be retried.","Run bd doctor database checks before bulk moves.","Keep DB grants current for the service account on both dependency tables."],"tags":["storage","sql","dependencies","collision-check"],"backgroundTag":"dependency-retarget-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}