Conversation
|
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Thank you for following the naming conventions for pull request titles! 🙏 |
📝 WalkthroughWalkthroughAdds a concurrency test and implements retryable transactional flows in key create/update handlers to handle identity-creation races and MySQL deadlocks; also introduces a DB deadlock detector utility. Identity lookup/creation, ratelimit/permission/role handling, audit logging, and error wrapping are moved into retry-aware transactions. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant API as API (CreateKey)
participant TX as DB (Write Tx)
participant RO as DB (Read)
Note right of API: retry loop up to 3 attempts on retryable errors
Client->>API: POST /v2/keys (externalId, workspace, params)
API->>TX: Begin Tx -> try INSERT Identity
alt insert succeeds
TX-->>API: identityID
else duplicate or deadlock
API->>RO: FindIdentity(workspace, externalId)
RO-->>API: existing identityID (or not found)
alt found
API->>TX: proceed using existing identityID
else not found
API->>TX: retry or propagate retryable error
end
end
API->>TX: INSERT Key, encryption, permissions, ratelimits, roles, audit logs
TX-->>API: Commit
API-->>Client: 200 { keyId, identityId }
sequenceDiagram
autonumber
actor Client
participant API as API (UpdateKey)
participant TX as DB (Write Tx)
participant RO as DB (Read)
Note right of API: retry loop up to 3 attempts for identity/deadlock races
Client->>API: PATCH /v2/keys/{id} (may include externalId, updates)
API->>TX: Begin Tx -> conditional INSERT Identity if externalId provided
alt insert succeeds
TX-->>API: identityID
else duplicate or deadlock
API->>RO: FindIdentity(workspace, externalId)
RO-->>API: existing identityID
API->>TX: proceed (or retry)
end
API->>TX: apply updates, adjust ratelimits, permissions, roles, audit logs
TX-->>API: Commit
API-->>Client: 200 { updated key payload }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
go/apps/api/routes/v2_keys_update_key/handler.go (1)
186-190: Avoid replica lag on post-duplicate lookup; prefer primary (RW) or add a short retry.Using RO here escapes the transactional snapshot, but if RO points to a replica, replication lag can still make this lookup miss and return 500. Use the primary or add a bounded retry.
Suggested minimal change:
- identity, err = db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + identity, err = db.Query.FindIdentity(ctx, h.DB.RW(), db.FindIdentityParams{Optionally add a tiny retry (e.g., 3 attempts with 5–20ms backoff) if you must keep RO for load-shedding.
Is h.DB.RO() a replica in production? If yes, please switch to RW here or add a retry to tolerate lag.
go/apps/api/routes/v2_keys_create_key/handler.go (1)
236-259: Post-duplicate lookup should hit primary; consider bounded retry and reuse.Good fix to bypass tx snapshot. However, using RO may read from a replica and miss the just-created row due to lag.
- Prefer primary (RW) for the lookup:
- identity, err = db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + identity, err = db.Query.FindIdentity(ctx, h.DB.RW(), db.FindIdentityParams{
- Optionally add a short bounded retry if you must keep RO.
- Consider extracting this get-or-create-identity logic into a shared helper to keep create/update paths consistent.
Is RO a replica in production? If so, please switch to RW here or add a retry to prevent rare 500s from replica lag.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
go/apps/api/routes/v2_keys_create_key/200_test.go(1 hunks)go/apps/api/routes/v2_keys_create_key/handler.go(1 hunks)go/apps/api/routes/v2_keys_update_key/handler.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
go/apps/api/routes/v2_keys_create_key/handler.go (5)
go/pkg/db/handle_err_duplicate_key.go (1)
IsDuplicateKeyError(7-13)go/pkg/fault/wrap.go (3)
Wrap(25-67)Internal(75-89)Public(97-111)go/pkg/codes/unkey_application.go (1)
App(53-71)go/pkg/db/identity_find.sql_generated.go (1)
FindIdentityParams(20-24)go/pkg/db/models_generated.go (1)
Identity(629-638)
go/apps/api/routes/v2_keys_update_key/handler.go (2)
go/pkg/db/queries.go (1)
Query(29-29)go/pkg/db/identity_find.sql_generated.go (1)
FindIdentityParams(20-24)
go/apps/api/routes/v2_keys_create_key/200_test.go (4)
go/pkg/testutil/http.go (3)
NewHarness(55-189)TestResponse(241-246)CallRoute(271-305)go/pkg/testutil/seed/seed.go (2)
CreateApiRequest(84-92)Resources(23-28)go/pkg/db/identity_find.sql_generated.go (1)
FindIdentityParams(20-24)go/pkg/db/models_generated.go (1)
Identity(629-638)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Test Go API Local / Test
- GitHub Check: Test API / API Test Local
- GitHub Check: Build / Build
- GitHub Check: Analyze (javascript-typescript)
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
go/apps/api/routes/v2_keys_create_key/200_test.go (1)
235-247: Stop callingtestutil.CallRoutefrom goroutines
CallRoutestill usesrequire.*on the parent*testing.T; invoking it from worker goroutines is not goroutine-safe and will panic if an assertion ever triggers. We already noted this in the previous review—please switch the workers to a non-asserting helper that returns(TestResponse, error)and push any failure through the channel (e.g. add the suggestedCallRouteNoAssertintestutiland use it here).- res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) - if res.Status != 200 { - errors <- fmt.Errorf("unexpected status code: %d", res.Status) - return - } - results <- res + res, callErr := testutil.CallRouteNoAssert[handler.Request, handler.Response](h, route, headers, req) + if callErr != nil { + errors <- callErr + return + } + if res.Status != 200 { + errors <- fmt.Errorf("unexpected status code: %d", res.Status) + return + } + results <- res
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
go/apps/api/routes/v2_keys_create_key/200_test.go(1 hunks)go/apps/api/routes/v2_keys_create_key/handler.go(1 hunks)go/apps/api/routes/v2_keys_update_key/handler.go(1 hunks)go/pkg/db/handle_err_deadlock.go(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-14T16:25:48.167Z
Learnt from: Flo4604
PR: unkeyed/unkey#3785
File: go/apps/api/routes/v2_keys_reroll_key/401_test.go:52-61
Timestamp: 2025-08-14T16:25:48.167Z
Learning: User Flo4604 requested creation of a GitHub issue to track converting all test files to use table-driven test patterns as a broader codebase improvement, following the suggestion made during review of go/apps/api/routes/v2_keys_reroll_key/401_test.go.
Applied to files:
go/apps/api/routes/v2_keys_create_key/200_test.go
🧬 Code graph analysis (3)
go/apps/api/routes/v2_keys_create_key/200_test.go (4)
go/pkg/testutil/http.go (3)
NewHarness(55-189)TestResponse(241-246)CallRoute(271-305)go/pkg/testutil/seed/seed.go (1)
CreateApiRequest(84-92)go/pkg/db/identity_find.sql_generated.go (1)
FindIdentityParams(20-24)go/pkg/db/models_generated.go (1)
Identity(629-638)
go/apps/api/routes/v2_keys_update_key/handler.go (15)
go/pkg/db/tx.go (1)
Tx(196-201)go/pkg/db/key_update.sql_generated.go (1)
UpdateKeyParams(51-70)go/pkg/db/queries.go (2)
Query(29-29)BulkQuery(31-31)go/pkg/db/identity_find.sql_generated.go (1)
FindIdentityParams(20-24)go/pkg/db/handle_err_no_rows.go (1)
IsNotFound(8-10)go/pkg/db/identity_insert.sql_generated.go (1)
InsertIdentityParams(31-38)go/pkg/db/handle_err_duplicate_key.go (1)
IsDuplicateKeyError(7-13)go/pkg/db/handle_err_deadlock.go (1)
IsDeadlockError(9-16)go/pkg/db/ratelimit_list_by_key_id.sql_generated.go (1)
ListRatelimitsByKeyIDRow(24-30)go/pkg/db/key_insert_ratelimit.sql_generated.go (1)
InsertKeyRatelimitParams(39-49)go/pkg/db/permission_find_by_slugs.sql_generated.go (1)
FindPermissionsBySlugsParams(17-20)go/pkg/db/permission_insert.sql_generated.go (1)
InsertPermissionParams(33-40)go/pkg/db/key_permission_insert.sql_generated.go (1)
InsertKeyPermissionParams(27-33)go/pkg/db/role_find_by_names.sql_generated.go (2)
FindRolesByNamesParams(17-20)FindRolesByNamesRow(22-25)go/pkg/db/key_role_insert.sql_generated.go (1)
InsertKeyRoleParams(27-32)
go/apps/api/routes/v2_keys_create_key/handler.go (16)
go/pkg/db/tx.go (1)
Tx(196-201)go/pkg/db/key_insert.sql_generated.go (1)
InsertKeyParams(51-67)go/pkg/db/identity_find.sql_generated.go (1)
FindIdentityParams(20-24)go/pkg/db/models_generated.go (6)
Identity(629-638)Environment(618-627)AuditLog(527-543)Permission(704-712)Key(640-664)Role(772-779)go/pkg/db/handle_err_no_rows.go (1)
IsNotFound(8-10)go/pkg/db/identity_insert.sql_generated.go (1)
InsertIdentityParams(31-38)go/pkg/db/handle_err_duplicate_key.go (1)
IsDuplicateKeyError(7-13)go/pkg/db/handle_err_deadlock.go (1)
IsDeadlockError(9-16)go/pkg/db/key_encryption_insert.sql_generated.go (1)
InsertKeyEncryptionParams(18-24)go/pkg/db/key_insert_ratelimit.sql_generated.go (1)
InsertKeyRatelimitParams(39-49)go/pkg/db/permission_find_by_slugs.sql_generated.go (1)
FindPermissionsBySlugsParams(17-20)go/pkg/db/permission_insert.sql_generated.go (1)
InsertPermissionParams(33-40)go/pkg/db/key_permission_insert.sql_generated.go (1)
InsertKeyPermissionParams(27-33)go/pkg/auditlog/events.go (3)
AuthConnectPermissionKeyEvent(55-55)AuthConnectRoleKeyEvent(53-53)KeyCreateEvent(20-20)go/pkg/db/role_find_by_names.sql_generated.go (2)
FindRolesByNamesParams(17-20)FindRolesByNamesRow(22-25)go/pkg/db/key_role_insert.sql_generated.go (1)
InsertKeyRoleParams(27-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Test Go API Local / Test
- GitHub Check: Test API / API Test Local
- GitHub Check: Build / Build
- GitHub Check: Analyze (javascript-typescript)
There was a problem hiding this comment.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
go/apps/api/routes/v2_keys_create_key/handler.go(1 hunks)go/apps/api/routes/v2_keys_update_key/handler.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
go/apps/api/routes/v2_keys_update_key/handler.go (17)
go/pkg/db/tx.go (1)
Tx(196-201)go/pkg/db/key_update.sql_generated.go (1)
UpdateKeyParams(51-70)go/pkg/db/queries.go (2)
Query(29-29)BulkQuery(31-31)go/pkg/db/identity_find.sql_generated.go (1)
FindIdentityParams(20-24)go/pkg/db/handle_err_no_rows.go (1)
IsNotFound(8-10)go/pkg/uid/uid.go (3)
IdentityPrefix(28-28)RatelimitPrefix(29-29)PermissionPrefix(27-27)go/pkg/db/identity_insert.sql_generated.go (1)
InsertIdentityParams(31-38)go/pkg/db/handle_err_duplicate_key.go (1)
IsDuplicateKeyError(7-13)go/pkg/db/handle_err_deadlock.go (1)
IsDeadlockError(9-16)go/pkg/db/ratelimit_list_by_key_id.sql_generated.go (1)
ListRatelimitsByKeyIDRow(24-30)go/pkg/db/key_insert_ratelimit.sql_generated.go (1)
InsertKeyRatelimitParams(39-49)go/pkg/db/permission_find_by_slugs.sql_generated.go (1)
FindPermissionsBySlugsParams(17-20)go/pkg/db/permission_insert.sql_generated.go (1)
InsertPermissionParams(33-40)go/pkg/auditlog/events.go (2)
PermissionCreateEvent(46-46)KeyUpdateEvent(22-22)go/pkg/db/key_permission_insert.sql_generated.go (1)
InsertKeyPermissionParams(27-33)go/pkg/db/role_find_by_names.sql_generated.go (1)
FindRolesByNamesRow(22-25)go/pkg/db/key_role_insert.sql_generated.go (1)
InsertKeyRoleParams(27-32)
go/apps/api/routes/v2_keys_create_key/handler.go (17)
go/pkg/db/tx.go (1)
Tx(196-201)go/pkg/db/key_insert.sql_generated.go (1)
InsertKeyParams(51-67)go/pkg/db/queries.go (2)
Query(29-29)BulkQuery(31-31)go/pkg/db/identity_find.sql_generated.go (1)
FindIdentityParams(20-24)go/pkg/db/models_generated.go (6)
Identity(629-638)Environment(618-627)AuditLog(527-543)Permission(704-712)Key(640-664)Role(772-779)go/pkg/db/handle_err_no_rows.go (1)
IsNotFound(8-10)go/pkg/db/identity_insert.sql_generated.go (1)
InsertIdentityParams(31-38)go/pkg/db/handle_err_duplicate_key.go (1)
IsDuplicateKeyError(7-13)go/pkg/db/handle_err_deadlock.go (1)
IsDeadlockError(9-16)go/pkg/db/key_encryption_insert.sql_generated.go (1)
InsertKeyEncryptionParams(18-24)go/pkg/db/key_insert_ratelimit.sql_generated.go (1)
InsertKeyRatelimitParams(39-49)go/pkg/db/permission_find_by_slugs.sql_generated.go (1)
FindPermissionsBySlugsParams(17-20)go/pkg/db/permission_insert.sql_generated.go (1)
InsertPermissionParams(33-40)go/pkg/db/key_permission_insert.sql_generated.go (1)
InsertKeyPermissionParams(27-33)go/pkg/auditlog/events.go (3)
AuthConnectPermissionKeyEvent(55-55)AuthConnectRoleKeyEvent(53-53)KeyCreateEvent(20-20)go/pkg/db/role_find_by_names.sql_generated.go (2)
FindRolesByNamesParams(17-20)FindRolesByNamesRow(22-25)go/pkg/db/key_role_insert.sql_generated.go (1)
InsertKeyRoleParams(27-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Test API / API Test Local
- GitHub Check: Test Go API Local / Test
- GitHub Check: Build / Build
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
go/apps/api/routes/v2_keys_create_key/handler.go (3)
181-183: Clarify retry comment.The comment "up to 2 times" is ambiguous—it could mean "2 attempts" or "2 retries." Given the loop
for attempt := range 3yields three iterations (0, 1, 2), this provides an initial attempt plus 2 retries (3 attempts total). Please update the comment to say "up to 2 retries (3 attempts total)" for clarity.- // Retry transaction up to 2 times on deadlock or identity creation race + // Retry transaction up to 2 retries (3 attempts total) on deadlock or identity creation race
377-417: Consider documenting permission auto-creation behavior.Permissions are automatically created if they don't exist (lines 377-417), whereas roles must exist beforehand (lines 499-502). This behavioral difference might be intentional based on your domain model, but it could be confusing to API consumers. Consider documenting this distinction in your API documentation or adding a comment explaining the rationale.
592-603: Consider adding exponential backoff between retries.While not critical for correctness, adding a small delay (e.g., 10ms, 50ms, 100ms) between retry attempts could reduce contention for deadlock scenarios. This would give other transactions time to complete and release locks.
For example, add a brief sleep after determining a retry is needed:
if !isRetryable { break } // Brief backoff to reduce contention time.Sleep(time.Duration(attempt+1) * 10 * time.Millisecond)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
go/apps/api/routes/v2_keys_create_key/handler.go(1 hunks)go/apps/api/routes/v2_keys_update_key/handler.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
go/apps/api/routes/v2_keys_create_key/handler.go (12)
go/pkg/db/tx.go (1)
Tx(196-201)go/pkg/db/key_insert.sql_generated.go (1)
InsertKeyParams(51-67)go/pkg/db/identity_find.sql_generated.go (1)
FindIdentityParams(20-24)go/pkg/db/identity_insert.sql_generated.go (1)
InsertIdentityParams(31-38)go/pkg/db/handle_err_duplicate_key.go (1)
IsDuplicateKeyError(7-13)go/pkg/db/handle_err_deadlock.go (1)
IsDeadlockError(9-16)go/pkg/db/key_encryption_insert.sql_generated.go (1)
InsertKeyEncryptionParams(18-24)go/pkg/db/key_insert_ratelimit.sql_generated.go (1)
InsertKeyRatelimitParams(39-49)go/pkg/db/permission_find_by_slugs.sql_generated.go (1)
FindPermissionsBySlugsParams(17-20)go/pkg/db/permission_insert.sql_generated.go (1)
InsertPermissionParams(33-40)go/pkg/db/key_permission_insert.sql_generated.go (1)
InsertKeyPermissionParams(27-33)go/pkg/db/role_find_by_names.sql_generated.go (2)
FindRolesByNamesParams(17-20)FindRolesByNamesRow(22-25)
go/apps/api/routes/v2_keys_update_key/handler.go (15)
go/pkg/db/tx.go (1)
Tx(196-201)go/pkg/db/key_update.sql_generated.go (1)
UpdateKeyParams(51-70)go/pkg/db/queries.go (2)
Query(29-29)BulkQuery(31-31)go/pkg/db/identity_find.sql_generated.go (1)
FindIdentityParams(20-24)go/pkg/db/identity_insert.sql_generated.go (1)
InsertIdentityParams(31-38)go/pkg/db/handle_err_duplicate_key.go (1)
IsDuplicateKeyError(7-13)go/pkg/db/handle_err_deadlock.go (1)
IsDeadlockError(9-16)go/pkg/db/ratelimit_list_by_key_id.sql_generated.go (1)
ListRatelimitsByKeyIDRow(24-30)go/pkg/db/key_insert_ratelimit.sql_generated.go (1)
InsertKeyRatelimitParams(39-49)go/pkg/db/permission_find_by_slugs.sql_generated.go (1)
FindPermissionsBySlugsParams(17-20)go/pkg/db/permission_insert.sql_generated.go (1)
InsertPermissionParams(33-40)go/pkg/auditlog/events.go (2)
PermissionCreateEvent(46-46)KeyUpdateEvent(22-22)go/pkg/db/key_permission_insert.sql_generated.go (1)
InsertKeyPermissionParams(27-33)go/pkg/db/role_find_by_names.sql_generated.go (2)
FindRolesByNamesParams(17-20)FindRolesByNamesRow(22-25)go/pkg/db/key_role_insert.sql_generated.go (1)
InsertKeyRoleParams(27-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build / Build
- GitHub Check: Test API / API Test Local
- GitHub Check: Test Go API Local / Test
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
go/apps/api/routes/v2_keys_update_key/handler.go (5)
110-116: Retry loop correctly implements 3-attempt strategy.The retry loop structure properly handles up to 3 attempts for transient failures (deadlocks and identity creation races). The use of
for attempt := range 3yields attempts 0, 1, and 2, which is correct.
146-198: LGTM! Identity race condition handling is correct.The code properly fixes the race condition described in the PR objectives:
- Attempts to find existing identity within the transaction (lines 154-158)
- Creates new identity if not found (lines 171-178)
- Crucially, returns unwrapped errors for duplicate key and deadlock cases (lines 181-183), allowing the retry loop to handle concurrent identity creation
- On retry, the
FindIdentitycall will succeed because the concurrent request created itThis ensures that concurrent API calls creating the same identity no longer result in 500 errors.
604-615: Retry condition logic is sound.The differentiated retry strategy is appropriate:
- Deadlocks: Always retryable (within the 3-attempt loop)
- Duplicate keys: Retryable only on first 2 attempts (
attempt < 2)This makes sense because duplicate key errors after 2 retries likely indicate a persistent conflict rather than a transient race condition, warranting an immediate failure rather than continued retries.
617-627: Error handling after retry exhaustion is correct.The final error handling properly distinguishes between retryable errors (wrapped with retry exhaustion context) and other errors (returned as-is). The error messages correctly reference the "update key" operation context.
✓ Past review comment addressed: Error messages updated from "failed to create identity" to "failed to update key after retries".
231-302: Credits handling provides flexible control.The credit management logic allows independent control of
RemainingandRefillfields:
- Specifying
Remaining: nullclears remaining requests and refill settings (lines 244-249)- Specifying
Refillsubsequently re-enables refilling (lines 264-298)This "last specified wins" pattern provides flexibility for scenarios like clearing current credits but setting a new refill schedule. The validation for monthly vs. daily intervals (lines 275-294) properly enforces constraints.
go/apps/api/routes/v2_keys_create_key/handler.go (3)
209-255: LGTM: Identity race condition handling is correct.The identity lookup-or-create logic properly handles concurrent creation races:
- Searches for the identity within the transaction (line 213)
- If not found, attempts to create it (lines 230-237)
- Duplicate key and deadlock errors are returned unwrapped (lines 240-242), allowing the retry loop to detect and handle them
- Other errors are properly wrapped with fault context
This correctly addresses the PR objective of fixing the identity concurrency creation error.
287-294: LGTM: Good validation for monthly refill.The validation correctly enforces that
refillDaymust be provided when the refill interval ismonthly, preventing invalid configurations.
592-615: LGTM: Retry logic is correct.The retry logic properly handles both deadlocks and identity creation races:
- Breaks on success (lines 593-595)
- Retries deadlocks for all attempts
- Retries duplicate key errors for attempts 0 and 1 (giving 3 total attempts as intended)
- Wraps exhausted retryable errors with appropriate messages (lines 607-612)
- Returns non-retryable errors as-is (line 614)
The error messages correctly reference "key" creation (addressing the past review comment).
Graphite Automations"Post a GIF when PR approved" took an action on this PR • (10/07/25)1 gif was posted to this PR based on Andreas Thomas's automation. |

What does this PR do?
Fixes #4024
This fixes the case when we can't find a identity in our current transaction, but it got created concurrently via another api call and when we insert we get a duplicate key error.
Instead we now just search for the identity again in our normal Read only connection instead of throwing a 500 errror.
Type of change
How should this be tested?
Tests, and trying to create this error by concurrently creating the same identity
Checklist
Required
pnpm buildpnpm fmtconsole.logsgit pull origin mainAppreciated