plandex-ai/plandex · error
panic in operation: %v %s
Error message
panic in operation: %v %s
What it means
When the queue executes an operation, a deferred recover() captures any panic raised by the operation body, logs it with a stack trace, and converts it into `panic in operation: %v\n%s`. The opErr is then applied to the operation and, for write-scope ops with clearRepoOnErr, the repo is marked for rollback so partial writes don't persist.
Source
Thrown at app/server/db/queue.go:252
if locksVerboseLogging {
log.Printf("[Queue] Operation %s (%s) context canceled", op.id, op.reason)
}
op.done <- op.ctx.Err()
default:
if locksVerboseLogging {
log.Printf("[Queue] Starting operation %s (%s)", op.id, op.reason)
}
// actually do the operation
var opErr error
func() {
defer func() {
panicErr := recover()
if panicErr != nil {
log.Printf("[Queue] Panic in operation %s (%s): %v", op.id, op.reason, panicErr)
log.Printf("[Queue] Stack trace: %s", string(debug.Stack()))
opErr = fmt.Errorf("panic in operation: %v\n%s", panicErr, string(debug.Stack()))
}
if opErr != nil && op.scope == LockScopeWrite && op.clearRepoOnErr {
if locksVerboseLogging {
log.Printf("[Queue] Operation %s (%s) failed with error, marking for rollback: %v",
op.id, op.reason, opErr)
}
needsRollback = true
}
}()
if locksVerboseLogging {
log.Printf("[Queue] Executing operation %s (%s)", op.id, op.reason)
}
opErr = op.op(repo)
if locksVerboseLogging {
if opErr != nil {
log.Printf("[Queue] Operation %s (%s) failed with error: %v", op.id, op.reason, opErr)View on GitHub (pinned to e2d772072e)
Solutions
- Read the attached stack trace to find the panicking line in the operation callback
- Fix the underlying nil/bounds bug in the op function
- Note that write-scope ops with clearRepoOnErr will be rolled back — verify repo state after a panic
- Move preconditions/validation into the op before touching shared state to avoid panics
Example fix
// before
repo.Update(key, mutate(val))
// after
if val == nil {
opErr = fmt.Errorf("cannot mutate nil value for key %s", key)
return
}
repo.Update(key, mutate(val)) Defensive patterns
Strategy: try-catch
Validate before calling
// before queuing, ensure the op callback's inputs are non-nil
if key == "" || repo == nil {
return errors.New("invalid operation: key and repo must be set")
} Try / catch
err := <-op.done
if err != nil && strings.Contains(err.Error(), "panic in operation") {
log.Printf("queued op panicked; stack embedded in error: %v", err)
// write-scope ops with clearRepoOnErr were marked for rollback; reload repo state
} Prevention
- Validate op inputs before enqueueing to avoid nil-deref panics
- Keep operation callbacks pure with respect to shared repo state
- Set clearRepoOnErr on write ops so partial writes are rolled back
- Alert on '[Queue] Panic in operation' logs to catch buggy callbacks early
When it happens
Trigger: A queued operation's callback panics — nil dereference, index out of range, or any unhandled panic inside the op function executed by the queue worker.
Common situations: Buggy operation callbacks touching uninitialized state, panics from lower-layer helpers on unexpected data, races on shared repo state between concurrent ops.
Related errors
- panic in UpdateContexts: %v\n%s
- panic in GetPlanConvo: %v\n%s
- panic in gitRemoveIndexLockFileIfExists: %v %s
- panic in SyncPlanTokens: %v %s
- panic in DeleteDraftPlans: %v %s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/c951151fe6561014.
Report an issue: GitHub.