plandex-ai/plandex · error
planId is required
Error message
planId is required
What it means
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.
Source
Thrown at app/server/db/locks.go:99
case <-params.Ctx.Done():
return "", params.Ctx.Err()
case <-time.After(initialJitter):
}
orgId := params.OrgId
userId := params.UserId
planId := params.PlanId
branch := params.Branch
scope := params.Scope
planBuildId := params.PlanBuildId
ctx := params.Ctx
cancelFn := params.CancelFn
if orgId == "" {
return "", fmt.Errorf("orgId is required")
}
if planId == "" {
return "", fmt.Errorf("planId is required")
}
if scope != LockScopeRead && scope != LockScopeWrite {
return "", fmt.Errorf("invalid lock scope: %s", scope)
}
tx, err := Conn.BeginTxx(ctx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
if err != nil {
if locksVerboseLogging {
log.Printf("[Lock][%d] Error starting transaction %v | reason: %s",
goroutineID, err, params.Reason)
}
return "", fmt.Errorf("error starting transaction: %v", err)
}
var committed bool
// Ensure that rollback is attempted in case of failure
defer func() {View on GitHub (pinned to e2d772072e)
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.
Example fix
// before
params := db.LockRepoParams{OrgId: orgId, UserId: userId, Scope: db.LockScopeWrite}
// after
if planId == "" { return fmt.Errorf("planId missing") }
params := db.LockRepoParams{OrgId: orgId, PlanId: planId, UserId: userId, Scope: db.LockScopeWrite} Defensive patterns
Strategy: validation
Validate before calling
func validateLockParams(p db.LockRepoParams) error {
if p.PlanId == "" { return errors.New("planId must be set before locking") }
return nil
} Type guard
func hasPlanId(p db.LockRepoParams) bool { return strings.TrimSpace(p.PlanId) != "" } Try / catch
if err := validateLockParams(params); err != nil { return err }
lockId, err := db.LockRepo(ctx, cancel, params)
if err != nil { return fmt.Errorf("lock failed: %w", err) } Prevention
- 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.
When it happens
Trigger: Building LockRepoParams without PlanId, e.g. locking before a plan is created, or passing an empty plan id parsed from a request path/body.
Common situations: 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.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- orgId is required
- invalid lock scope: %s
- invalid lock scope: %v
- error prompting host: %v
- error prompting email: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/86468f3efb00391f.
Report an issue: GitHub.