{"record":{"id":"e9e059aefac7dc06","repo":"gastownhall/beads","slug":"failed-to-check-issue-existence-w-e9e059","errorCode":null,"errorMessage":"failed to check issue existence: %w","messagePattern":"failed to check issue existence: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/close.go","lineNumber":361,"sourceCode":"\t`, issueTable), types.StatusClosed, now, now, reason, session, freshRowLock(), id, types.StatusClosed)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to close issue: %w\", err)\n\t}\n\n\trows, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get rows affected: %w\", err)\n\t}\n\tif rows == 0 {\n\t\tvar status string\n\t\tqerr := tx.QueryRowContext(ctx,\n\t\t\tfmt.Sprintf(`SELECT status FROM %s WHERE id = ?`, issueTable), id,\n\t\t).Scan(&status)\n\t\tif qerr == sql.ErrNoRows {\n\t\t\treturn nil, fmt.Errorf(\"%w: issue %s\", storage.ErrNotFound, id)\n\t\t}\n\t\tif qerr != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to check issue existence: %w\", qerr)\n\t\t}\n\t\tif types.Status(status) == types.StatusClosed {\n\t\t\treturn &CloseResult{IsWisp: isWisp, AlreadyClosed: true}, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to close issue: %s\", id)\n\t}\n\n\t// A closed issue holds no lease (no-op for wisps, which are never leased).\n\tif err := DeleteLeaseInTx(ctx, tx, id); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif recordEvent {\n\t\tif err := RecordEventInTable(ctx, tx, eventTable, id, types.EventClosed, actor, reason); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to record event: %w\", err)\n\t\t}\n\t}\n","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/close.go#L343-L379","documentation":"This error wraps a failure of the follow-up SELECT status FROM issues WHERE id = ? that runs when the close UPDATE matched zero rows. That SELECT is needed to distinguish 'already closed' from 'not found'; if the query itself errors (driver, connection, scan), the close is aborted with this wrapped error. The original driver error is preserved via %w.","triggerScenarios":"tx.QueryRowContext(...).Scan() returns a non-ErrNoRows error during the existence check: connection failure inside the transaction, context cancellation, driver error, or datatype mismatch while scanning into the status string.","commonSituations":"Connection dropped between the failed UPDATE and the diagnostic SELECT; context timeout expiring mid-function; corrupted row data preventing scan; low-level driver bug under load.","solutions":["Inspect the wrapped error with errors.Is/As to find the root cause (timeout, connection reset, scan type mismatch).","Retry the close operation in a fresh transaction after confirming the DB is reachable.","Increase the context deadline if timeouts are killing the diagnostic query.","Check server logs for the failing query; verify the issues table schema is intact (bd doctor)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := store.CloseIssue(ctx, id, actor, reason)\nif err != nil && strings.Contains(err.Error(), \"failed to check issue existence\") {\n    if errors.Is(err, context.DeadlineExceeded) || isTransientNet(err) {\n        return retryClose(ctx, id, actor, reason, 1)\n    }\n    return err\n}","preventionTips":["Use a generous context deadline so the diagnostic SELECT isn't cut off.","Keep the connection stable — avoid canceling mid-operation.","Verify schema integrity with bd doctor if scan errors recur.","Retry transient network failures with backoff at the caller level."],"tags":["database","sql","query","transaction"],"backgroundTag":"sql-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}