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

  1. Set LockRepoParams.PlanId to the persisted plan's id before acquiring the lock.
  2. Reorder logic so plan creation/persistence happens before any locking call.
  3. 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

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


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/86468f3efb00391f. Report an issue: GitHub.