{"record":{"id":"86468f3efb00391f","repo":"plandex-ai/plandex","slug":"planid-is-required","errorCode":null,"errorMessage":"planId is required","messagePattern":"planId is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/locks.go","lineNumber":99,"sourceCode":"\tcase <-params.Ctx.Done():\n\t\treturn \"\", params.Ctx.Err()\n\tcase <-time.After(initialJitter):\n\t}\n\n\torgId := params.OrgId\n\tuserId := params.UserId\n\tplanId := params.PlanId\n\tbranch := params.Branch\n\tscope := params.Scope\n\tplanBuildId := params.PlanBuildId\n\tctx := params.Ctx\n\tcancelFn := params.CancelFn\n\n\tif orgId == \"\" {\n\t\treturn \"\", fmt.Errorf(\"orgId is required\")\n\t}\n\tif planId == \"\" {\n\t\treturn \"\", fmt.Errorf(\"planId is required\")\n\t}\n\tif scope != LockScopeRead && scope != LockScopeWrite {\n\t\treturn \"\", fmt.Errorf(\"invalid lock scope: %s\", scope)\n\t}\n\n\ttx, err := Conn.BeginTxx(ctx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})\n\tif err != nil {\n\t\tif locksVerboseLogging {\n\t\t\tlog.Printf(\"[Lock][%d] Error starting transaction %v | reason: %s\",\n\t\t\t\tgoroutineID, err, params.Reason)\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"error starting transaction: %v\", err)\n\t}\n\n\tvar committed bool\n\n\t// Ensure that rollback is attempted in case of failure\n\tdefer func() {","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/locks.go#L81-L117","documentation":"Sentinel validation in lockRepoDB: the request's PlanId field is empty, so a plan lock cannot be scoped. Input validation before beginning the DB transaction; a sibling guard checks orgId and lock scope similarly.","triggerScenarios":"Building LockRepoParams without PlanId, e.g. locking before a plan is created, or passing an empty plan id parsed from a request path/body.","commonSituations":"A plan creation flow that starts the build lock before persisting the plan row; API client omitting planId; string parsing yielding \"\" when the id is missing from a URL segment.","solutions":["Set LockRepoParams.PlanId to the persisted plan's id before acquiring the lock.","Reorder logic so plan creation/persistence happens before any locking call.","Validate planId non-empty in the HTTP handler and return 400 before reaching the lock layer."],"exampleFix":"// before\nparams := db.LockRepoParams{OrgId: orgId, UserId: userId, Scope: db.LockScopeWrite}\n// after\nif planId == \"\" { return fmt.Errorf(\"planId missing\") }\nparams := db.LockRepoParams{OrgId: orgId, PlanId: planId, UserId: userId, Scope: db.LockScopeWrite}","handlingStrategy":"validation","validationCode":"func validateLockParams(p db.LockRepoParams) error {\n    if p.PlanId == \"\" { return errors.New(\"planId must be set before locking\") }\n    return nil\n}","typeGuard":"func hasPlanId(p db.LockRepoParams) bool { return strings.TrimSpace(p.PlanId) != \"\" }","tryCatchPattern":"if err := validateLockParams(params); err != nil { return err }\nlockId, err := db.LockRepo(ctx, cancel, params)\nif err != nil { return fmt.Errorf(\"lock failed: %w\", err) }","preventionTips":["Only lock plans that are already persisted — create the plan row first, then lock.","Validate planId in the HTTP handler and return 400 before the DB layer.","Never parse-and-trust plan ids from URL segments without an emptiness check."],"tags":["validation","locking","input-validation"],"backgroundTag":"missing-required-argument","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}