{"record":{"id":"2daab962d66cd581","repo":"plandex-ai/plandex","slug":"error-inserting-new-lock-v","errorCode":null,"errorMessage":"error inserting new lock: %v","messagePattern":"error inserting new lock: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/locks.go","lineNumber":355,"sourceCode":"\t\tnewLock.PlanBuildId,\n\t\tnewLock.Scope,\n\t\tnewLock.Branch,\n\t).Scan(&insertedId)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\t// Means ON CONFLICT DO NOTHING prevented insertion\n\t\t\t// => concurrency conflict => backoff & retry\n\t\t\treturn retryWithExponentialBackoff(params.Ctx,\n\t\t\t\terrors.New(\"lock conflict: row not inserted\"),\n\t\t\t\tnumRetry,\n\t\t\t\tfunc(nextAttempt int) (string, error) {\n\t\t\t\t\treturn lockRepoDB(params, nextAttempt)\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\n\t\tlog.Printf(\"[Lock][%d] error inserting new lock: %v | reason: %s\", goroutineID, err, params.Reason)\n\t\treturn \"\", fmt.Errorf(\"error inserting new lock: %v\", err)\n\t}\n\n\tif insertedId.Valid {\n\t\tnewLock.Id = insertedId.String\n\t} else {\n\t\tif locksVerboseLogging {\n\t\t\tlog.Printf(\"no rows returned from insert query, means there was a conflict\")\n\t\t}\n\t\treturn retryWithExponentialBackoff(params.Ctx, err, numRetry, func(nextAttempt int) (string, error) {\n\t\t\treturn lockRepoDB(params, nextAttempt)\n\t\t})\n\t}\n\n\tif locksVerboseLogging {\n\t\tlog.Printf(\"[Lock][%d] INSERT took %v | reason: %s\",\n\t\t\tgoroutineID, time.Since(insertStart), params.Reason)\n\t}\n","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/locks.go#L337-L373","documentation":"lockRepoDB wraps any failure of the INSERT INTO repo_locks ... ON CONFLICT DO NOTHING statement (other than the no-rows conflict case) into 'error inserting new lock'. It signals that the lock row could not be created in Postgres, so the plan-level repo lock was not acquired. The original database error is embedded in the message via %v.","triggerScenarios":"The INSERT returns a real DB error rather than sql.ErrNoRows: e.g. connection dropped mid-query, NOT NULL/FK violation on org_id, serialization failure (40001) or deadlock (40P01) under REPEATABLE READ, or context deadline exceeded on params.Ctx while the query runs.","commonSituations":"Postgres restarted or connection pool exhausted under load; concurrent plans deadlocking on the repo_locks unique index; invalid orgId referencing a missing orgs row; query canceled because the caller's context timed out before insert completed.","solutions":["Inspect the wrapped cause in the message (e.g. pq code 40001/40P01) — transient errors are retried by retryWithExponentialBackoff, so persistent ones usually mean a non-transient DB issue","Verify the repo_locks schema matches the insert columns (org_id, user_id, plan_id, plan_build_id, scope, branch) and FK targets exist","Check Postgres health: connection limits (max_connections), pool settings, and logs for deadlocks or cancellations at the same timestamp","Ensure the caller's context deadline is generous enough for the insert under normal load"],"exampleFix":"// before\nid, err := lockRepoDB(ctx, planId, orgId, scope)\nif err != nil { log.Fatal(err) }\n// after\nid, err := lockRepoDB(ctx, planId, orgId, scope)\nif err != nil {\n    var pqErr *pq.Error\n    if errors.As(err, &pqErr) && (pqErr.Code == \"40001\" || pqErr.Code == \"40P01\") {\n        // transient: rely on retry/backoff instead of failing hard\n    }\n    return fmt.Errorf(\"acquiring repo lock for plan %s: %w\", planId, err)\n}","handlingStrategy":"try-catch","validationCode":"if err := Conn.PingContext(ctx); err != nil {\n    return fmt.Errorf(\"db unavailable, skip lock acquisition: %w\", err)\n}","typeGuard":"var pqErr *pq.Error\nif errors.As(err, &pqErr) {\n    transient := pqErr.Code == \"40001\" || pqErr.Code == \"40P01\"\n}","tryCatchPattern":"id, err := lockRepoDB(ctx, orgId, planId, reason)\nif err != nil {\n    var pqErr *pq.Error\n    if errors.As(err, &pqErr) && (pqErr.Code == \"40001\" || pqErr.Code == \"40P01\") {\n        // transient: safe to retry later\n    }\n    return fmt.Errorf(\"acquire lock plan %s: %w\", planId, err)\n}","preventionTips":["Ping the DB or check pool health before acquiring locks","Give the acquisition context a deadline that covers retries","Keep repo_locks schema/FK data (org_id, plan_id) valid before calling","Monitor Postgres deadlock and serialization-failure logs"],"tags":["database","postgres","locking"],"backgroundTag":"database-insert-failed","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}