{"record":{"id":"2878c6e325bd996e","repo":"gastownhall/beads","slug":"coordinate-issue-create-w","errorCode":null,"errorMessage":"coordinate issue create: %w","messagePattern":"coordinate issue create: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/create_only_guard.go","lineNumber":22,"sourceCode":"\t\"context\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/steveyegge/beads/internal/storage\"\n)\n\n// EnsureIssueIDAvailableInTx serializes same-shard creates and rejects occupied IDs.\nfunc EnsureIssueIDAvailableInTx(ctx context.Context, tx DBTX, id string) error {\n\tif tx == nil {\n\t\treturn fmt.Errorf(\"ensure issue ID available: transaction is nil\")\n\t}\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"ensure issue ID available: ID is empty\")\n\t}\n\tkey := issueCreateCoordinationKey(id)\n\tif _, err := tx.ExecContext(ctx, \"REPLACE INTO local_metadata (`key`, value) VALUES (?, ?)\", key, strconv.FormatInt(FreshRowLock(), 10)); err != nil {\n\t\treturn fmt.Errorf(\"coordinate issue create: %w\", err)\n\t}\n\tfor _, table := range []string{\"issues\", \"wisps\"} {\n\t\tvar count int\n\t\tif err := tx.QueryRowContext(ctx, \"SELECT COUNT(*) FROM \"+table+\" WHERE id = ?\", id).Scan(&count); err != nil {\n\t\t\treturn fmt.Errorf(\"check %s for issue %q: %w\", table, id, err)\n\t\t}\n\t\tif count > 0 {\n\t\t\treturn fmt.Errorf(\"%w: %s\", storage.ErrAlreadyExists, id)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc issueCreateCoordinationKey(id string) string {\n\tsum := sha256.Sum256([]byte(id))\n\tshard := uint16(sum[0])<<4 | uint16(sum[1])>>4\n\treturn fmt.Sprintf(\"issue-create/v1/%03x\", shard)\n}","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/create_only_guard.go#L4-L40","documentation":"EnsureIssueIDAvailableInTx fails while writing the per-ID coordination row into local_metadata (REPLACE INTO local_metadata). This write serializes same-shard creates so two concurrent creates of the same ID cannot interleave; failure means the underlying transaction/DB rejected the write.","triggerScenarios":"The tx handle is dead or already failed (rolled back, closed connection); storage backend error on the local_metadata REPLACE; context canceled mid-exec.","commonSituations":"Long transactions that hit connection timeouts before reaching the guard; a DB in read-only or locked state; driver-level errors during concurrent batch imports.","solutions":["Inspect the wrapped driver error via errors.Unwrap to identify the storage-level cause","Retry the whole transaction with a fresh tx handle; never reuse a failed tx","Reduce transaction scope/duration so the connection does not time out before the guard runs"],"exampleFix":"// before\nerr := EnsureIssueIDAvailableInTx(ctx, staleTx, id) // tx already rolled back\n// after\ntx, err := db.BeginTx(ctx, nil)\nif err != nil { return err }\nerr = EnsureIssueIDAvailableInTx(ctx, tx, id)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := EnsureIssueIDAvailableInTx(ctx, tx, id); err != nil {\n    if strings.HasPrefix(err.Error(), \"coordinate issue create:\") {\n        cause := errors.Unwrap(err) // inspect driver error\n        // abandon tx, begin a new one, retry once\n    }\n    return err\n}","preventionTips":["Keep transactions short so the connection stays healthy","Retry at the transaction boundary, never inside a failed tx","Monitor DB locks/read-only state in the deployment"],"tags":["database","transaction","concurrency","coordination"],"backgroundTag":"issue-create-coordination-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}