{"record":{"id":"f79dec81bb8da885","repo":"vxcontrol/pentagi","slug":"failed-to-move-resource-q-w","errorCode":null,"errorMessage":"failed to move resource %q: %w","messagePattern":"failed to move resource %q: %w","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"backend/pkg/server/services/resources.go","lineNumber":1176,"sourceCode":"}\n\nfunc resourcesByPath(recs []models.UserResource) map[string]models.UserResource {\n\tbyPath := make(map[string]models.UserResource, len(recs))\n\tfor _, rec := range recs {\n\t\tbyPath[rec.Path] = rec\n\t}\n\treturn byPath\n}\n\nfunc updateMovedResource(tx *gorm.DB, rec models.UserResource, newPath string, now time.Time) (models.UserResource, error) {\n\tif err := tx.Model(&models.UserResource{}).\n\t\tWhere(\"id = ?\", rec.ID).\n\t\tUpdates(map[string]interface{}{\n\t\t\t\"path\":       newPath,\n\t\t\t\"name\":       path.Base(newPath),\n\t\t\t\"updated_at\": now,\n\t\t}).Error; err != nil {\n\t\treturn models.UserResource{}, fmt.Errorf(\"failed to move resource %q: %w\", rec.Path, err)\n\t}\n\trec.Path = newPath\n\trec.Name = path.Base(newPath)\n\trec.UpdatedAt = now\n\treturn rec, nil\n}\n\n// ---- POST /resources/copy --------------------------------------------------\n\n// CopyResource copies one or more resource files / directories to a new path.\n//\n// Single-source behaviour (exactly one unique source after dedup):\n//\n//\tDestination is the exact target path, inheriting existing trailing-slash\n//\tand existing-directory semantics (unchanged from original behaviour).\n//\n// Multi-source behaviour (two or more unique sources after dedup):\n//","sourceCodeStart":1158,"sourceCodeEnd":1194,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/services/resources.go#L1158-L1194","documentation":"updateMovedResource rewrites a user resource row's path/name/updated_at inside a DB transaction during a move. If the GORM Updates call fails (connection issue, constraint violation, deadlock, row lock), the move is aborted and this wrapped error carries the original path and the underlying DB error. It propagates up to moveFileResource / moveDirResourceToAbsentDestination / moveDirResourceMerge and the transaction is rolled back by the caller.","triggerScenarios":"Any move (REST move endpoint) whose UPDATE statement fails: database connection loss or timeout mid-transaction, a unique-index violation on (user_id, path) if another row already holds newPath, a deadlock with a concurrent write, or the DB being read-only / out of disk.","commonSituations":"Two users (or two browser tabs) racing to move resources to the same destination path; PostgreSQL restart or connection-pool exhaustion during a large directory move; a unique constraint on path added by a migration while legacy duplicate rows exist.","solutions":["Check the wrapped underlying DB error (errors.Unwrap / log) to identify whether it is a unique violation, deadlock, or connectivity problem.","Retry the move once; deadlocks and transient connection errors are usually retryable.","Pre-check that no resource already exists at newPath (findResourceByPath) or delete/archive the conflicting destination first.","Verify DB health: connection pool limits, disk space, and that migrations ran cleanly."],"exampleFix":"// before\nupdated, err := updateMovedResource(tx, entry, newPath, now)\nif err != nil { return result, err }\n// after\nupdated, err := updateMovedResource(tx, entry, newPath, now)\nif err != nil {\n    tx.Rollback()\n    if errors.Is(err, syscall.ECONNRESET) || isDeadlock(err) { /* retry once */ }\n    return result, err\n}","handlingStrategy":"try-catch","validationCode":"var existing models.UserResource\nerr := db.Where(\"user_id = ? AND path = ?\", uid, newPath).First(&existing).Error\nif err == nil { return errors.New(\"destination path already exists\") }","typeGuard":"func isUniqueViolation(err error) bool {\n    var pgErr *pgconn.PgError\n    return errors.As(err, &pgErr) && pgErr.Code == \"23505\"\n}","tryCatchPattern":"updated, err := updateMovedResource(tx, rec, newPath, now)\nif err != nil {\n    tx.Rollback()\n    if isUniqueViolation(err) || isDeadlock(err) { /* retry or surface 409 */ }\n    return fmt.Errorf(\"move %q failed: %w\", rec.Path, err)\n}","preventionTips":["Always check destination availability before moving.","Run moves inside short transactions to reduce deadlock windows.","Monitor DB logs for deadlocks and unique violations on user_resources.","Retry transient DB errors with exponential backoff."],"tags":["database","gorm","move","unique-constraint"],"backgroundTag":"database-constraint-violation","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}