Skip to content

feat(platform): improve invited user admin workflows#12471

Open
Swiftyos wants to merge 24 commits intodevfrom
swiftyos/remove-bulk-invite-limit
Open

feat(platform): improve invited user admin workflows#12471
Swiftyos wants to merge 24 commits intodevfrom
swiftyos/remove-bulk-invite-limit

Conversation

@Swiftyos
Copy link
Contributor

@Swiftyos Swiftyos commented Mar 18, 2026

This expands the invited-user admin flow so bulk uploads scale better, invited-user lookup is searchable, and Tally enrichment behaves more gracefully under slow LLM responses.

Changes 🏗️

  • Increased the bulk invite upload limit from 500 to 5,000 users and now fail oversized uploads explicitly instead of silently truncating rows
  • Batch-check existing users and existing invites before creating bulk invite rows so duplicates are skipped with clearer row-level results
  • Added invited-user search support to the admin API and OpenAPI schema, then wired the admin UI to use debounced search plus server-driven pagination
  • Reworked the admin invited-users page layout to surface invite creation, bulk upload, search, pagination, and Tally status actions more cleanly
  • Added Tally extraction retry/backoff handling with a 120 second per-attempt timeout, 10 attempts, and graceful timeout logging instead of noisy tracebacks for exhausted retries
  • Serialized invited-user Tally precompute calls with a small delay to better respect downstream rate limits
  • Extended backend tests to cover bulk invite prechecks, the 5,000-user cap, Tally retry success, Tally retry exhaustion, and graceful timeout handling
  • No configuration changes were required

Checklist 📋

For code changes:

  • I have clearly listed my changes in the PR description
  • I have made a test plan
  • I have tested my changes according to the test plan:
    • poetry run pytest backend/api/features/admin/user_admin_routes_test.py backend/data/invited_user_test.py backend/data/tally_test.py
    • PATH="$HOME/.nvm/versions/node/v22.21.1/bin:$PATH" pnpm generate:api
    • PATH="$HOME/.nvm/versions/node/v22.21.1/bin:$PATH" pnpm lint
    • PATH="$HOME/.nvm/versions/node/v22.21.1/bin:$PATH" pnpm types

For configuration changes:

  • .env.default is updated or already compatible with my changes
  • docker-compose.yml is updated or already compatible with my changes
  • I have included a list of my configuration changes in the PR description (under Changes)

@Swiftyos Swiftyos requested a review from a team as a code owner March 18, 2026 10:38
@Swiftyos Swiftyos requested review from kcze and majdyz and removed request for a team March 18, 2026 10:38
@github-project-automation github-project-automation bot moved this to 🆕 Needs initial review in AutoGPT development kanban Mar 18, 2026
@github-actions github-actions bot added platform/frontend AutoGPT Platform - Front end platform/backend AutoGPT Platform - Back end labels Mar 18, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Added server-side search and pagination for invited users; expanded bulk-invite to a two-phase validate+batch-create flow with higher row limit (5000) and duplicate pre-checks; introduced concurrency and rate-limiting for tally seed extraction with retry/timeout handling and a new TallyExtractionTimeoutError; corresponding frontend paging/search UI changes.

Changes

Cohort / File(s) Summary
Backend: invited-user core
autogpt_platform/backend/backend/data/invited_user.py
Reworked bulk-invite to two phases (serial validate/dedupe → chunked batch create), increased MAX_BULK_INVITE_ROWS 500→5000, added pre-check _fetch_existing_emails, per-row CREATED/SKIPPED/ERROR classification, improved masking/logging, added concurrency controls for tally enrichment (_tally_semaphore, _TALLY_RATE_DELAY) and consolidated update payloads.
Backend: tally extraction & handling
autogpt_platform/backend/backend/data/tally.py
Added retryable LLM extraction with increased per-attempt timeout (30→120s), exponential backoff, _LLM_MAX_ATTEMPTS, and new TallyExtractionTimeoutError; extract loop retries on TimeoutError and raises the new error after exhaustion; populate_understanding_from_tally updated to handle timeout error explicitly.
Backend: API route
autogpt_platform/backend/backend/api/features/admin/user_admin_routes.py
Admin endpoint for invited users accepts new search query param and forwards it (or None) to list_invited_users to enable server-side filtering.
Backend: tests
autogpt_platform/backend/backend/data/invited_user_test.py, autogpt_platform/backend/backend/data/tally_test.py
Updated/added tests: mocking _fetch_existing_emails, new tests for skipping existing emails, rejecting >5000 rows, and tally timeout handling; tests adapted for new retry/timeout semantics and new exception type.
Frontend: admin UI — page & hooks
autogpt_platform/frontend/src/app/(platform)/admin/users/useAdminUsersPage.ts, .../AdminUsersPage/AdminUsersPage.tsx
Added client-side pagination and debounced search state (currentPage, searchQuery, handleSearchChange); calls API with paginated/debounced search param; exposes pagination/search to child components.
Frontend: InvitedUsersTable
autogpt_platform/frontend/src/app/(platform)/admin/users/components/InvitedUsersTable/InvitedUsersTable.tsx
Added props for pagination, currentPage, searchQuery, onPageChange, onSearchChange; UI now shows search input, total items, and Previous/Next pagination controls.
Frontend: BulkInviteForm label
autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
Updated file-input label text to reflect new maximum ("Maximum 5,000 users per upload.") (UI text only).
Frontend: OpenAPI spec
autogpt_platform/frontend/src/app/api/openapi.json
Added optional search query parameter to GET /api/users/admin/invited-users spec.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Admin UI
    participant API as Admin API
    participant InvitedService as InvitedUser Service
    participant Parser as File Parser
    participant Validator as Validator/Deduper
    participant BatchCreator as Batch Creator
    participant DB as Database
    participant Tally as Tally/LLM
    rect rgba(200,230,201,0.5)
    UI->>API: upload bulk-invite file (with page/search)
    API->>InvitedService: submit file for processing
    end
    InvitedService->>Parser: parse CSV/TXT -> rows
    Parser->>Validator: validate rows serially (dedupe, normalize)
    Validator->>InvitedService: return validated_rows
    InvitedService->>BatchCreator: chunk validated_rows -> create tasks
    BatchCreator->>DB: batch create invites (parallel via asyncio.gather)
    DB-->>BatchCreator: per-row responses (created / precondition / error)
    BatchCreator->>InvitedService: aggregate CREATED/SKIPPED/ERROR
    InvitedService->>Tally: async enqueue/compute tally seed (rate-limited + semaphore)
    Tally->>InvitedService: tally result or TallyExtractionTimeoutError
    InvitedService->>API: return aggregated results and statuses
    API->>UI: respond with created/skipped/errors and pagination
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Review effort 3/5

Suggested reviewers

  • kcze
  • Pwuts
  • Bentlybro

Poem

🐰 I checked each row with careful hops,
Deduped the list and stitched the drops.
When LLMs dawdle, I wait and try,
I log the skips and mask each sigh.
Hooray — five thousand invites, then a carrot pie 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main objective of improving invited user admin workflows, covering the primary enhancements to scalability, searchability, and Tally robustness.
Description check ✅ Passed The PR description comprehensively outlines all major changes, including bulk invite scaling, search support, Tally retry handling, and layout improvements, directly matching the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch swiftyos/remove-bulk-invite-limit
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 2026

🔍 PR Overlap Detection

This check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early.

🔴 Merge Conflicts Detected

The following PRs have been tested and will have merge conflicts if merged after this PR. Consider coordinating with the authors.

  • feat(platform): add nightly copilot automation flow #12407 (Swiftyos · updated 1d ago)
    • 📁 autogpt_platform/
      • backend/backend/copilot/prompting.py (1 conflict, ~13 lines)
      • backend/backend/copilot/tools/__init__.py (1 conflict, ~5 lines)
      • backend/backend/data/invited_user.py (1 conflict, ~528 lines)
      • backend/backend/notifications/test_notifications.py (1 conflict, ~4 lines)
      • backend/poetry.lock (1 conflict, ~5 lines)
      • frontend/src/app/api/openapi.json (1 conflict, ~25 lines)

Summary: 1 conflict(s), 0 medium risk, 0 low risk (out of 1 PRs with file overlap)


Auto-generated on push. Ignores: openapi.json, lock files.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx (1)

57-57: Clarify or enforce the 1 MB limit in the UI flow

Line 57 states a hard limit, but the current submit path does not pre-validate file size client-side. Consider either (a) adding a client-side file.size check before submit, or (b) adjusting copy to “Server limit: 1 MB” to avoid misleading UX on rejected uploads.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@autogpt_platform/frontend/src/app/`(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
at line 57, The UI currently states "UTF-8 encoded, max 1 MB file size." but the
submit flow in BulkInviteForm does not pre-validate file size; add a client-side
file.size check in the file input handler or submit handler (e.g., onFileChange
or handleSubmit in BulkInviteForm) to reject files > 1_048_576 bytes and show a
user-facing error before sending to the server, or alternatively change the copy
to "Server limit: 1 MB" to make the restriction declarative; update the
component’s validation logic (and any error state like setError or
validationMessage used by BulkInviteForm) so the UI prevents submission when the
file is too large.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@autogpt_platform/frontend/src/app/`(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx:
- Line 57: The UI currently states "UTF-8 encoded, max 1 MB file size." but the
submit flow in BulkInviteForm does not pre-validate file size; add a client-side
file.size check in the file input handler or submit handler (e.g., onFileChange
or handleSubmit in BulkInviteForm) to reject files > 1_048_576 bytes and show a
user-facing error before sending to the server, or alternatively change the copy
to "Server limit: 1 MB" to make the restriction declarative; update the
component’s validation logic (and any error state like setError or
validationMessage used by BulkInviteForm) so the UI prevents submission when the
file is too large.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ec21bef-f095-47d3-8810-70af270dc3ef

📥 Commits

Reviewing files that changed from the base of the PR and between e657472 and 5bdbd72.

📒 Files selected for processing (2)
  • autogpt_platform/backend/backend/data/invited_user.py
  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
💤 Files with no reviewable changes (1)
  • autogpt_platform/backend/backend/data/invited_user.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: integration_test
  • GitHub Check: check API types
  • GitHub Check: Seer Code Review
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: type-check (3.12)
  • GitHub Check: type-check (3.13)
  • GitHub Check: type-check (3.11)
  • GitHub Check: end-to-end tests
  • GitHub Check: Analyze (python)
  • GitHub Check: Check PR Status
🧰 Additional context used
📓 Path-based instructions (12)
autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}: Use Node.js 21+ with pnpm package manager for frontend development
Always run 'pnpm format' for formatting and linting code in frontend development

autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}: Run pnpm format to auto-fix formatting issues before completing work
Run pnpm lint to check for lint errors and fix any that appear before completing work

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
autogpt_platform/frontend/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{tsx,ts}: Use function declarations for components and handlers (not arrow functions) in React components
Only use arrow functions for small inline lambdas (map, filter, etc.) in React components
Use PascalCase for component names and camelCase with 'use' prefix for hook names in React
Use Tailwind CSS utilities only for styling in frontend components
Use design system components from 'src/components/' (atoms, molecules, organisms) in frontend development
Never use 'src/components/legacy/' in frontend code
Only use Phosphor Icons (@phosphor-icons/react) for icons in frontend components
Use generated API hooks from '@/app/api/generated/endpoints/' instead of deprecated 'BackendAPI' or 'src/lib/autogpt-server-api/
'
Use React Query for server state (via generated hooks) in frontend development
Default to client components ('use client') in Next.js; only use server components for SEO or extreme TTFB needs
Use '' component for rendering errors in frontend UI; use toast notifications for mutation errors; use 'Sentry.captureException()' for manual exceptions
Separate render logic from data/behavior in React components; keep comments minimal (code should be self-documenting)

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
autogpt_platform/frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{ts,tsx}: No barrel files or 'index.ts' re-exports in frontend code
Regenerate API hooks with 'pnpm generate:api' after backend OpenAPI spec changes in frontend development

Run pnpm types to check for type errors and fix any that appear before completing work

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
autogpt_platform/frontend/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

autogpt_platform/frontend/**/*.{js,jsx,ts,tsx}: Format frontend code using pnpm format
Never use components from src/components/__legacy__/*

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
autogpt_platform/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

autogpt_platform/frontend/src/**/*.{ts,tsx}: Structure components as ComponentName/ComponentName.tsx + useComponentName.ts + helpers.ts and use design system components from src/components/ (atoms, molecules, organisms)
Use generated API hooks from @/app/api/__generated__/endpoints/ with pattern use{Method}{Version}{OperationName} and regenerate with pnpm generate:api
Use function declarations (not arrow functions) for components and handlers
Separate render logic from business logic with component.tsx + useComponent.ts + helpers.ts structure
Colocate state when possible, avoid creating large components, use sub-components in local /components folder
Avoid large hooks, abstract logic into helpers.ts files when sensible
Use arrow functions only for callbacks, not for component declarations
Avoid comments at all times unless the code is very complex
Do not use useCallback or useMemo unless asked to optimize a given function

autogpt_platform/frontend/src/**/*.{ts,tsx}: Use function declarations (not arrow functions) for components and handlers
Use type-safe generated API hooks via Orval + React Query for data fetching
Use React Query for server state management and co-locate UI state in components/hooks
Separate render logic (.tsx) from business logic (use*.ts hooks)
Use only shadcn/ui (Radix UI primitives) with Tailwind CSS for UI components
Use Phosphor Icons only for all icon implementations
Use ErrorCard component for render errors, toast for mutations, and Sentry for exceptions
Use design system components from src/components/ (atoms, molecules, organisms)
Never use src/components/__legacy__/* components
Use generated API hooks from @/app/api/__generated__/endpoints/ with pattern use{Method}{Version}{OperationName}
Use Tailwind CSS only for styling with design tokens
Do not use useCallback or useMemo unless asked to optimize a specific function
Never type with any unless a variable/attribute can actually be of any type

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
autogpt_platform/frontend/**/*.{js,jsx,ts,tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS only for styling, use design tokens, and use Phosphor Icons only

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
autogpt_platform/frontend/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Component props should be interface Props { ... } (not exported) unless the interface needs to be used outside the component

Use type Props = { ... } (not exported) for component props unless used outside the component

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
autogpt_platform/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never type with any, if no types available use unknown

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
autogpt_platform/frontend/src/app/(platform)/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

If adding protected frontend routes, update frontend/lib/supabase/middleware.ts

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
autogpt_platform/frontend/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (autogpt_platform/frontend/CLAUDE.md)

Fully capitalize acronyms in symbols, e.g. graphID, useBackendAPI

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
autogpt_platform/frontend/src/**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (autogpt_platform/frontend/CLAUDE.md)

Put sub-components in a local components/ folder within the feature directory

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
autogpt_platform/frontend/src/**/[A-Z]*/**/*.{ts,tsx}

📄 CodeRabbit inference engine (autogpt_platform/frontend/CLAUDE.md)

Structure components as ComponentName/ComponentName.tsx + useComponentName.ts + helpers.ts

Files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
🧠 Learnings (2)
📓 Common learnings
Learnt from: Swiftyos
Repo: Significant-Gravitas/AutoGPT PR: 12347
File: autogpt_platform/backend/backend/data/invited_user.py:193-193
Timestamp: 2026-03-10T11:22:18.867Z
Learning: In Significant-Gravitas/AutoGPT, the admin data-layer functions in `autogpt_platform/backend/backend/data/invited_user.py` (`list_invited_users`, `create_invited_user`, `revoke_invited_user`, `retry_invited_user_tally`, `bulk_create_invited_users_from_file`) intentionally omit an acting-user/admin ID parameter. Authorization for these functions is enforced entirely at the FastAPI router layer via `Security(requires_admin_user)` in `user_admin_routes.py`. Do not flag the absence of a user_id/actor_id parameter in these functions as a missing data-access guardrail violation.
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/frontend/src/app/api/openapi.json:11897-11900
Timestamp: 2026-03-04T23:58:18.476Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12284`
Backend/frontend OpenAPI codegen convention: In backend/api/features/store/model.py, the StoreSubmission and StoreSubmissionAdminView models define submitted_at: datetime | None, changes_summary: str | None, and instructions: str | None with no default. This is intentional to produce “required but nullable” fields in OpenAPI (properties appear in required[] and use anyOf [type, null]). This matches Prisma’s submittedAt DateTime? and changesSummary String?. Do not flag this as a required/nullable mismatch.
📚 Learning: 2026-02-27T10:45:49.499Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12213
File: autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunMCPTool/helpers.tsx:23-24
Timestamp: 2026-02-27T10:45:49.499Z
Learning: Prefer using generated OpenAPI types from '@/app/api/__generated__/' for payloads defined in openapi.json (e.g., MCPToolsDiscoveredResponse, MCPToolOutputResponse). Use inline TypeScript interfaces only for payloads that are SSE-stream-only and not exposed via OpenAPI. Apply this pattern to frontend tool components (e.g., RunMCPTool) and related areas where similar SSE/openapi-discrepancies occur; avoid re-implementing types when a generated type is available.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx

majdyz added 2 commits March 18, 2026 17:54
Removing the limit without batching allowed unbounded sequential DB
writes and asyncio tasks per request. Raise the cap from 500 → 5000
to support larger uploads while avoiding connection pool exhaustion.

Update frontend helper text to reflect the new 5 000-row limit.
Replace the hard row limit with a two-pass approach:
1. Validate and deduplicate emails serially (cheap).
2. Call create_invited_user in concurrent batches of 500 via
   asyncio.gather, bounding DB load while allowing unlimited rows
   within the 1 MB file-size constraint.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
autogpt_platform/backend/backend/data/invited_user.py (1)

405-490: ⚠️ Potential issue | 🟡 Minor

Preserve input row order in BulkInvitedUsersResult.results.

Current two-pass appends can reorder rows (e.g., first-pass errors/skips are appended before valid-row outcomes). This can confuse consumers that render results in list order. Emit results in original parsed row order.

💡 Suggested fix (keep deterministic row order)
@@
-    results: list[BulkInvitedUserRowResult] = []
+    row_results: dict[int, BulkInvitedUserRowResult] = {}
@@
-            results.append(
-                BulkInvitedUserRowResult(
-                    row_number=row.row_number,
-                    email=row.email or None,
-                    name=row_name,
-                    status="ERROR",
-                    message="Invalid email address",
-                )
-            )
+            row_results[row.row_number] = BulkInvitedUserRowResult(
+                row_number=row.row_number,
+                email=row.email or None,
+                name=row_name,
+                status="ERROR",
+                message="Invalid email address",
+            )
             continue
@@
-            results.append(
-                BulkInvitedUserRowResult(
-                    row_number=row.row_number,
-                    email=normalized_email,
-                    name=row_name,
-                    status="SKIPPED",
-                    message="Duplicate email in upload file",
-                )
-            )
+            row_results[row.row_number] = BulkInvitedUserRowResult(
+                row_number=row.row_number,
+                email=normalized_email,
+                name=row_name,
+                status="SKIPPED",
+                message="Duplicate email in upload file",
+            )
             continue
@@
-                results.append(
-                    BulkInvitedUserRowResult(
-                        row_number=row.row_number,
-                        email=normalized_email,
-                        name=row_name,
-                        status="SKIPPED",
-                        message=str(outcome),
-                    )
-                )
+                row_results[row.row_number] = BulkInvitedUserRowResult(
+                    row_number=row.row_number,
+                    email=normalized_email,
+                    name=row_name,
+                    status="SKIPPED",
+                    message=str(outcome),
+                )
@@
-                results.append(
-                    BulkInvitedUserRowResult(
-                        row_number=row.row_number,
-                        email=normalized_email,
-                        name=row_name,
-                        status="ERROR",
-                        message="Unexpected error creating invite",
-                    )
-                )
+                row_results[row.row_number] = BulkInvitedUserRowResult(
+                    row_number=row.row_number,
+                    email=normalized_email,
+                    name=row_name,
+                    status="ERROR",
+                    message="Unexpected error creating invite",
+                )
@@
-                results.append(
-                    BulkInvitedUserRowResult(
-                        row_number=row.row_number,
-                        email=normalized_email,
-                        name=row_name,
-                        status="CREATED",
-                        message="Invite created",
-                        invited_user=outcome,
-                    )
-                )
+                row_results[row.row_number] = BulkInvitedUserRowResult(
+                    row_number=row.row_number,
+                    email=normalized_email,
+                    name=row_name,
+                    status="CREATED",
+                    message="Invite created",
+                    invited_user=outcome,
+                )
 
+    ordered_results = [row_results[row.row_number] for row in parsed_rows]
     return BulkInvitedUsersResult(
         created_count=created_count,
         skipped_count=skipped_count,
         error_count=error_count,
-        results=results,
+        results=ordered_results,
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/data/invited_user.py` around lines 405 -
490, Validation and creation currently append to results in two passes which can
reorder rows; instead, allocate a results_by_row_number dict keyed by
row.row_number and populate it during the first pass (set entries for invalid
emails and duplicate skips and increment error_count/skipped_count) and during
the second pass (set entries for PreconditionFailed/skipped, unexpected errors,
and successful creates and update skipped_count/error_count/created_count
accordingly). After both passes, rebuild BulkInvitedUsersResult.results by
iterating the original parsed_rows in order and collecting
results_by_row_number[row.row_number] so the final results list preserves the
input row order; use the same types (BulkInvitedUserRowResult) and the existing
symbols (parsed_rows, valid_rows, _BULK_INVITE_BATCH_SIZE, create_invited_user,
mask_email) when implementing.
🧹 Nitpick comments (1)
autogpt_platform/backend/backend/data/invited_user.py (1)

48-48: Consider making _BULK_INVITE_BATCH_SIZE environment/config tunable.

A fixed value of 500 may be too high/low depending on DB pool sizing per deployment. Making this configurable would reduce operational retuning via code changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/data/invited_user.py` at line 48, The
hardcoded _BULK_INVITE_BATCH_SIZE should be made configurable by reading from an
environment/config variable (e.g., os.getenv("BULK_INVITE_BATCH_SIZE")) with a
fallback default of 500; locate the _BULK_INVITE_BATCH_SIZE constant in
invited_user.py and replace its literal with code that reads the env var, casts
to int, validates/clamps to a sensible >0 value, and uses the default if parsing
fails, and update any related tests or docs to note the new
BULK_INVITE_BATCH_SIZE config option.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@autogpt_platform/backend/backend/data/invited_user.py`:
- Around line 405-490: Validation and creation currently append to results in
two passes which can reorder rows; instead, allocate a results_by_row_number
dict keyed by row.row_number and populate it during the first pass (set entries
for invalid emails and duplicate skips and increment error_count/skipped_count)
and during the second pass (set entries for PreconditionFailed/skipped,
unexpected errors, and successful creates and update
skipped_count/error_count/created_count accordingly). After both passes, rebuild
BulkInvitedUsersResult.results by iterating the original parsed_rows in order
and collecting results_by_row_number[row.row_number] so the final results list
preserves the input row order; use the same types (BulkInvitedUserRowResult) and
the existing symbols (parsed_rows, valid_rows, _BULK_INVITE_BATCH_SIZE,
create_invited_user, mask_email) when implementing.

---

Nitpick comments:
In `@autogpt_platform/backend/backend/data/invited_user.py`:
- Line 48: The hardcoded _BULK_INVITE_BATCH_SIZE should be made configurable by
reading from an environment/config variable (e.g.,
os.getenv("BULK_INVITE_BATCH_SIZE")) with a fallback default of 500; locate the
_BULK_INVITE_BATCH_SIZE constant in invited_user.py and replace its literal with
code that reads the env var, casts to int, validates/clamps to a sensible >0
value, and uses the default if parsing fails, and update any related tests or
docs to note the new BULK_INVITE_BATCH_SIZE config option.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c8ddee36-59f1-481d-a1b4-b8912ddd54bd

📥 Commits

Reviewing files that changed from the base of the PR and between c2d1cf4 and a5230f2.

📒 Files selected for processing (2)
  • autogpt_platform/backend/backend/data/invited_user.py
  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
✅ Files skipped from review due to trivial changes (1)
  • autogpt_platform/frontend/src/app/(platform)/admin/users/components/BulkInviteForm/BulkInviteForm.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: check API types
  • GitHub Check: end-to-end tests
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: Check PR Status
🧰 Additional context used
📓 Path-based instructions (6)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
autogpt_platform/backend/backend/data/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All data access in backend requires user ID checks; verify this for any 'data/*.py' changes

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
autogpt_platform/backend/**/*.{py,txt}

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use poetry run prefix for all Python commands, including testing, linting, formatting, and migrations

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
autogpt_platform/backend/backend/**/*.py

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use Prisma ORM for database operations in PostgreSQL with pgvector for embeddings

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
autogpt_platform/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
autogpt_platform/**/data/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For changes touching data/*.py, validate user ID checks or explain why not needed

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
🧠 Learnings (5)
📓 Common learnings
Learnt from: Swiftyos
Repo: Significant-Gravitas/AutoGPT PR: 12347
File: autogpt_platform/backend/backend/data/invited_user.py:193-193
Timestamp: 2026-03-10T11:22:18.867Z
Learning: In Significant-Gravitas/AutoGPT, the admin data-layer functions in `autogpt_platform/backend/backend/data/invited_user.py` (`list_invited_users`, `create_invited_user`, `revoke_invited_user`, `retry_invited_user_tally`, `bulk_create_invited_users_from_file`) intentionally omit an acting-user/admin ID parameter. Authorization for these functions is enforced entirely at the FastAPI router layer via `Security(requires_admin_user)` in `user_admin_routes.py`. Do not flag the absence of a user_id/actor_id parameter in these functions as a missing data-access guardrail violation.
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/frontend/src/app/api/openapi.json:11897-11900
Timestamp: 2026-03-04T23:58:18.476Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12284`
Backend/frontend OpenAPI codegen convention: In backend/api/features/store/model.py, the StoreSubmission and StoreSubmissionAdminView models define submitted_at: datetime | None, changes_summary: str | None, and instructions: str | None with no default. This is intentional to produce “required but nullable” fields in OpenAPI (properties appear in required[] and use anyOf [type, null]). This matches Prisma’s submittedAt DateTime? and changesSummary String?. Do not flag this as a required/nullable mismatch.
📚 Learning: 2026-03-10T11:22:14.861Z
Learnt from: Swiftyos
Repo: Significant-Gravitas/AutoGPT PR: 12347
File: autogpt_platform/backend/backend/data/invited_user.py:193-193
Timestamp: 2026-03-10T11:22:14.861Z
Learning: In autogpt_platform/backend/backend/data/invited_user.py, the admin data-layer functions (list_invited_users, create_invited_user, revoke_invited_user, retry_invited_user_tally, bulk_create_invited_users_from_file) intentionally omit an acting-user/admin ID parameter. Authorization is enforced exclusively at the FastAPI router layer via Security(requires_admin_user) in user_admin_routes.py. Do not flag the absence of a user_id/actor_id parameter in these functions as a missing data-access guardrail violation; rely on the router-level authorization. When reviewing related code, verify that any function that bypasses a parameter for performance or separation of concerns is complemented by equivalent security checks at the boundary (router) and that tests cover that authorization path in the router rather than in the data layer.

Applied to files:

  • autogpt_platform/backend/backend/data/invited_user.py
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/data/invited_user.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/data/invited_user.py
📚 Learning: 2026-03-16T16:35:40.236Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/api/features/workflow_import.py:54-63
Timestamp: 2026-03-16T16:35:40.236Z
Learning: Avoid using the word 'competitor' in public-facing identifiers and text. Use neutral naming for API paths, model names, function names, and UI text. Examples: rename 'CompetitorFormat' to 'SourcePlatform', 'convert_competitor_workflow' to 'convert_workflow', '/competitor-workflow' to '/workflow'. Apply this guideline to files under autogpt_platform/backend and autogpt_platform/frontend.

Applied to files:

  • autogpt_platform/backend/backend/data/invited_user.py
🔇 Additional comments (1)
autogpt_platform/backend/backend/data/invited_user.py (1)

449-477: Good per-row failure handling and masked logging.

Mapping PreconditionFailed to SKIPPED and masking emails for unexpected failures keeps batch processing resilient while reducing sensitive data exposure in logs.

The two-pass approach (validate first, then batch-create) causes
validation failures to appear before creation results in the list.
Sort by row_number so results always match input file order.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
autogpt_platform/backend/backend/data/invited_user.py (1)

441-447: Consider a semaphore for finer-grained concurrency control.

Each create_invited_user issues 2-3 DB queries, so 500 concurrent calls could spike to 1000-1500 concurrent DB operations per batch. If the connection pool is smaller (e.g., 20-50 connections), this may cause contention.

An optional enhancement is to wrap the gather with a semaphore:

_DB_CONCURRENCY = asyncio.Semaphore(50)

async def _create_with_limit(email: str, name: str | None):
    async with _DB_CONCURRENCY:
        return await create_invited_user(email, name)

This is optional since batching already bounds the total concurrent work per request and the current approach was the accepted solution from prior review.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/data/invited_user.py` around lines 441 -
447, The current batch loop uses asyncio.gather(*(create_invited_user(...)))
which can still yield many simultaneous DB queries; introduce a module-level
asyncio.Semaphore (e.g., _DB_CONCURRENCY = asyncio.Semaphore(50)) and a small
wrapper coroutine (e.g., _create_with_limit(email, name)) that acquires the
semaphore before calling create_invited_user and releases on exit, then replace
the gather call to invoke _create_with_limit for each item in the batch instead
of calling create_invited_user directly; keep the existing batching via
_BULK_INVITE_BATCH_SIZE and return_exceptions=True.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@autogpt_platform/backend/backend/data/invited_user.py`:
- Around line 441-447: The current batch loop uses
asyncio.gather(*(create_invited_user(...))) which can still yield many
simultaneous DB queries; introduce a module-level asyncio.Semaphore (e.g.,
_DB_CONCURRENCY = asyncio.Semaphore(50)) and a small wrapper coroutine (e.g.,
_create_with_limit(email, name)) that acquires the semaphore before calling
create_invited_user and releases on exit, then replace the gather call to invoke
_create_with_limit for each item in the batch instead of calling
create_invited_user directly; keep the existing batching via
_BULK_INVITE_BATCH_SIZE and return_exceptions=True.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7f60bd17-e742-47d9-b17d-791c7ca65ed7

📥 Commits

Reviewing files that changed from the base of the PR and between a5230f2 and c15fe6b.

📒 Files selected for processing (1)
  • autogpt_platform/backend/backend/data/invited_user.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: check API types
  • GitHub Check: integration_test
  • GitHub Check: end-to-end tests
  • GitHub Check: Seer Code Review
  • GitHub Check: type-check (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: type-check (3.13)
  • GitHub Check: Analyze (python)
  • GitHub Check: type-check (3.12)
  • GitHub Check: Check PR Status
  • GitHub Check: conflicts
🧰 Additional context used
📓 Path-based instructions (6)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
autogpt_platform/backend/backend/data/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All data access in backend requires user ID checks; verify this for any 'data/*.py' changes

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
autogpt_platform/backend/**/*.{py,txt}

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use poetry run prefix for all Python commands, including testing, linting, formatting, and migrations

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
autogpt_platform/backend/backend/**/*.py

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use Prisma ORM for database operations in PostgreSQL with pgvector for embeddings

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
autogpt_platform/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
autogpt_platform/**/data/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For changes touching data/*.py, validate user ID checks or explain why not needed

Files:

  • autogpt_platform/backend/backend/data/invited_user.py
🧠 Learnings (5)
📓 Common learnings
Learnt from: Swiftyos
Repo: Significant-Gravitas/AutoGPT PR: 12347
File: autogpt_platform/backend/backend/data/invited_user.py:193-193
Timestamp: 2026-03-10T11:22:18.867Z
Learning: In Significant-Gravitas/AutoGPT, the admin data-layer functions in `autogpt_platform/backend/backend/data/invited_user.py` (`list_invited_users`, `create_invited_user`, `revoke_invited_user`, `retry_invited_user_tally`, `bulk_create_invited_users_from_file`) intentionally omit an acting-user/admin ID parameter. Authorization for these functions is enforced entirely at the FastAPI router layer via `Security(requires_admin_user)` in `user_admin_routes.py`. Do not flag the absence of a user_id/actor_id parameter in these functions as a missing data-access guardrail violation.
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/frontend/src/app/api/openapi.json:11897-11900
Timestamp: 2026-03-04T23:58:18.476Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12284`
Backend/frontend OpenAPI codegen convention: In backend/api/features/store/model.py, the StoreSubmission and StoreSubmissionAdminView models define submitted_at: datetime | None, changes_summary: str | None, and instructions: str | None with no default. This is intentional to produce “required but nullable” fields in OpenAPI (properties appear in required[] and use anyOf [type, null]). This matches Prisma’s submittedAt DateTime? and changesSummary String?. Do not flag this as a required/nullable mismatch.
📚 Learning: 2026-03-10T11:22:14.861Z
Learnt from: Swiftyos
Repo: Significant-Gravitas/AutoGPT PR: 12347
File: autogpt_platform/backend/backend/data/invited_user.py:193-193
Timestamp: 2026-03-10T11:22:14.861Z
Learning: In autogpt_platform/backend/backend/data/invited_user.py, the admin data-layer functions (list_invited_users, create_invited_user, revoke_invited_user, retry_invited_user_tally, bulk_create_invited_users_from_file) intentionally omit an acting-user/admin ID parameter. Authorization is enforced exclusively at the FastAPI router layer via Security(requires_admin_user) in user_admin_routes.py. Do not flag the absence of a user_id/actor_id parameter in these functions as a missing data-access guardrail violation; rely on the router-level authorization. When reviewing related code, verify that any function that bypasses a parameter for performance or separation of concerns is complemented by equivalent security checks at the boundary (router) and that tests cover that authorization path in the router rather than in the data layer.

Applied to files:

  • autogpt_platform/backend/backend/data/invited_user.py
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/data/invited_user.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/data/invited_user.py
📚 Learning: 2026-03-16T16:35:40.236Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/api/features/workflow_import.py:54-63
Timestamp: 2026-03-16T16:35:40.236Z
Learning: Avoid using the word 'competitor' in public-facing identifiers and text. Use neutral naming for API paths, model names, function names, and UI text. Examples: rename 'CompetitorFormat' to 'SourcePlatform', 'convert_competitor_workflow' to 'convert_workflow', '/competitor-workflow' to '/workflow'. Apply this guideline to files under autogpt_platform/backend and autogpt_platform/frontend.

Applied to files:

  • autogpt_platform/backend/backend/data/invited_user.py
🔇 Additional comments (3)
autogpt_platform/backend/backend/data/invited_user.py (3)

48-48: LGTM!

The renamed constant _BULK_INVITE_BATCH_SIZE with underscore prefix correctly signals internal use, and 500 is a sensible batch size for bounding concurrent DB operations per asyncio.gather call.


405-439: LGTM!

The first pass efficiently validates email format and deduplicates entries before any DB operations. Collecting (row, normalized_email, row_name) tuples preserves the original row number for accurate result reporting while avoiding redundant normalization in the second pass.


491-491: LGTM!

Sorting by row_number ensures results appear in input-file order despite the two-pass (validate-then-create) approach producing interleaved results.

majdyz
majdyz previously approved these changes Mar 18, 2026
@github-project-automation github-project-automation bot moved this from 🆕 Needs initial review to 👍🏼 Mergeable in AutoGPT development kanban Mar 18, 2026
@Swiftyos Swiftyos changed the title refactor(backend): Remove 500-row limit on bulk invite uploads feat(platform): improve invited user admin workflows Mar 18, 2026
…icant-Gravitas/AutoGPT into swiftyos/remove-bulk-invite-limit
Copy link
Contributor

@majdyz majdyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Verdict: 🚫 Request Changes — 1 blocker, 4 should-fixes need addressing before merge.

🔴 Blocker (1)

  • isinstance(outcome, BaseException) in _create_bulk_invite_results swallows KeyboardInterrupt/SystemExit — change to isinstance(outcome, Exception)

🟠 Should Fix (4)

  • response is None / RuntimeError in extract_business_understanding_from_tally is unreachable dead code — use assert or remove the sentinel
  • TallyExtractionTimeoutError(asyncio.TimeoutError) will be accidentally caught by except asyncio.TimeoutError / except OSError — inherit from Exception instead
  • For 5,000-row bulk uploads, 5,000 background Tally tasks are all scheduled immediately without backpressure — see inline comment for approach
  • asyncio.sleep(_TALLY_RATE_DELAY) fires even for users with no Tally submission, making O(n × 0.6s) unavoidable even for empty results

🟡 Nice to Have (2)

  • where: dict = {} should be typed as prisma.types.InvitedUserWhereInput
  • search: str = Query("") should be Optional[str] = Query(None) for clean API semantics

The overall structure of this PR is solid — the batch precheck, modular helpers, retry/backoff on Tally, and test coverage are all good work. The blocker and should-fixes are all small, targeted changes.

@github-project-automation github-project-automation bot moved this from 👍🏼 Mergeable to 🚧 Needs work in AutoGPT development kanban Mar 18, 2026
Pwuts and others added 6 commits March 19, 2026 12:03
…andler

Re-raises system-level exceptions (KeyboardInterrupt, SystemExit, etc.)
from asyncio.gather(return_exceptions=True) instead of silently swallowing
them as error results.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change TallyExtractionTimeoutError to inherit from Exception instead of
  asyncio.TimeoutError to prevent silent catch by upstream except OSError
  or except asyncio.TimeoutError handlers.
- Replace unreachable `response is None` RuntimeError with assert.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove _build_bulk_invited_user_row_result helper that wrapped
BulkInvitedUserRowResult with positional args. Use named kwargs
directly for readability at each call site.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use prisma.types.InvitedUserWhereInput and InvitedUserUpdateInput
instead of untyped dict for schema-aware query construction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only sleep after successful Tally API lookups. For bulk imports where
most users have no Tally record, this avoids burning 0.6s per 404.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d task

Instead of spawning up to 5,000 independent background tasks via
schedule_invited_user_tally_precompute, bulk uploads now skip per-row
scheduling and instead launch a single task that processes enrichment
serially. This bounds event-loop pressure and respects the existing
semaphores and rate limits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move public API functions (get_or_activate_user, check_invite_eligibility,
list_invited_users, create_invited_user, etc.) before their private
helpers so readers encounter high-level logic first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pwuts and others added 4 commits March 19, 2026 12:30
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update test_bulk_create_invited_users_limits_create_concurrency to
accept the new schedule_tally kwarg and mock _schedule_bulk_tally_enrichment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The assertion on fetch_sleep.await_count was racing with the 4th task's
sleep call. Python 3.12+ changed asyncio event loop scheduling, causing
the 4th task's sleep to not have completed when three_extractions_started
fires. Move the assertion after await asyncio.gather(*tasks) to make it
deterministic across Python versions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@autogpt-pr-reviewer autogpt-pr-reviewer bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 8 specialist reports are in. Now compiling the final verdict.


PR #12471 — feat(platform): improve invited user admin workflows
Author: Swiftyos (Swifty) | Requested by: Review Squad | Files: 313 changed (+38,958 / -4,001)

Key files: invited_user.py (+/-), tally.py (+/-), user_admin_routes.py (+), model.py (+/-), InvitedUsersTable.tsx (+/-), useAdminUsersPage.ts (+/-), BulkInviteForm.tsx (+/-), openapi.json (+/-)

🎯 Verdict: REQUEST_CHANGES

What This PR Does

Scales the admin invited-user workflow from a 500-user manual ceiling to a 5,000-user bulk pipeline. Bulk uploads now validate and deduplicate rows before batch creation, return per-row CREATED/SKIPPED/ERROR results, and run Tally form enrichment in a rate-limited background pipeline. The admin UI gains server-side search with debounced input and Previous/Next pagination. Tally LLM extraction gets retry logic with exponential backoff and a dedicated timeout error type.

Specialist Findings

🛡️ Security ✅ — All admin endpoints properly gated with Security(requires_admin_user) at router level. Search parameter uses Prisma's parameterized contains — no SQL injection risk. Email enumeration via _fetch_existing_emails is acceptable given admin-only access. The TOCTOU race in create_invited_user (check-then-create) is mitigated by UniqueViolationError catch. Minor: no max_length on the search query param, tally_understanding dict exposed in list responses, and no backend-side file-size validation for bulk upload (only client-side 1MB check).

🏗️ Architecture ⚠️ — Two-phase bulk design (validate/dedupe → parallel batch create) is sound. Route/data layer separation is clean. However:
⚠️ invited_user.py at ~1,087 lines is 3.5× the project's own 300-line guideline — mixes CRUD, activation logic, bulk pipeline, and Tally orchestration. Should split into 3 modules.
⚠️ Process-local semaphores (asyncio.Semaphore(1) and Semaphore(3)) don't work across multiple uvicorn workers — effective concurrency to Tally API becomes N_workers × cap, not the intended cap. The AsyncClusterLock handles idempotency but not rate limiting.
⚠️ _tally_seed_tasks is process-local mutable state — fire-and-forget tasks lost on restart with no persistent job queue.

Performance ⚠️ — Several efficiency concerns for the 5,000-user scale:
⚠️ Unindexed search on name column (invited_user.py:236-241): ILIKE '%search%' on name forces sequential scan. The email unique index also can't help due to leading wildcard. Need trigram GIN index for scale past ~5K rows.
⚠️ Redundant DB queries in bulk create: Each of 5,000 rows does 2 reads + 1 write despite pre-check already fetching existing emails — ~15,000 DB queries when ~5,000 would suffice with a dedicated _bulk_create_invited_user bypassing redundant checks.
⚠️ Serial Tally enrichment with 0.6s rate delay: For 5,000 users, Tally lookups alone take ~50 minutes in rate delays, plus LLM extraction time. The _tally_lookup_semaphore = Semaphore(1) serializes all lookups globally.
⚠️ Sequential _fetch_existing_emails chunks: 10 serial iterations for 5,000 emails could be parallelized.

🧪 Testing ⚠️ — Backend tests are solid (boundary cases, concurrency, retry/backoff verified). Frontend has zero test coverage for the new pagination/search/bulk-results UI.
⚠️ Missing: exactly-5000-rows boundary test (5001 tested, not 5000 success)
⚠️ Missing: empty file, all-duplicates file, search param forwarding route test
⚠️ Missing: revoke_invited_user on CLAIMED user, retry_invited_user_tally on REVOKED user
⚠️ Missing: any frontend component tests for InvitedUsersTable, useAdminUsersPage

📖 Quality ⚠️ — Readability grade: B. Well-structured pipeline with clear intermediate results, but:
⚠️ 17% docstring coverage (80% threshold). Public functions like create_invited_user, revoke_invited_user, list_invited_users lack docstrings entirely.
⚠️ _EmailValidatedInviteRow = tuple[...] should be a NamedTuple for self-documenting field access.
⚠️ Semaphore(1) should be asyncio.Lock() to communicate intent (functionally identical).
⚠️ Several debug log statements use f-strings instead of deferred %s interpolation per project style guide (tally.py:473).

📦 Product ✅ — All claimed features implemented and verified. UX is clear with per-row results, debounced search, and proper loading states.
⚠️ Empty search state says "No invited users yet" when it should say "No matching users" — misleading when search returns 0 results (InvitedUsersTable.tsx:166).
⚠️ No confirmation dialog for Revoke — immediate destructive action with no "Are you sure?" (InvitedUsersTable.tsx:232).
⚠️ Retry Tally button stays enabled during RUNNING status — admin could spam-click (backend guards against duplicates but UI should set expectations).

📬 Discussion ✅ — Extensive review history: 50+ inline comments across @majdyz (CHANGES_REQUESTED), @Pwuts, @coderabbitai, and @sentry[bot]. All critical/blocker issues from previous rounds have been addressed:

  • BaseException swallowing → fixed (re-raises system exceptions)
  • TallyExtractionTimeoutError wrong base class → fixed (inherits Exception)
  • Undefined _BULK_INVITE_BATCH_SIZE → fixed (replaced with semaphore)
  • 5,000 background tasks → fixed (serial background enrichment)
  • Prisma type safety → fixed (InvitedUserWhereInput / InvitedUserUpdateInput)
  • File ordering → fixed (full reorder)
    Remaining unaddressed: author's own self-review suggestions (NamedTuple, parallel chunk fetch), @majdyz's Query("")Optional[str] suggestion (resolved as won't-fix).

🔎 QA ✅ — Full live testing completed with screenshots. All features verified end-to-end:

  • Search filters correctly with live count update
  • Pagination controls render (hidden when ≤1 page, shown with smaller page_size)
  • Create invite form works: submit → table refresh → new user at top
  • Bulk invite label shows "Maximum 5,000 users and 1 MB per upload"
  • Admin gating verified (403 for non-admin)
  • Tally status displays correctly (FAILED with expected "API key not configured" in test env)

Admin Users Table
Search Results
After Create Invite

Blockers (Must Fix)

None — all previous blockers from @majdyz's review have been addressed.

Should Fix (Follow-up OK)

  1. invited_user.py — split into 3 modules (1,087 lines, 3.5× guideline). Bulk pipeline + Tally orchestration + CRUD should be separate files.
  2. invited_user.py:236-241 — add database index for search: name column is unindexed; ILIKE '%term%' on both email and name will degrade at scale. Add trigram GIN index or at minimum @@index([name]).
  3. invited_user.py:262-292 — deduplicate DB queries in bulk create: Pre-check already fetches existing emails, but each create_invited_user call re-checks. A dedicated _bulk_create_invited_user would cut ~10,000 redundant queries for a 5,000-row upload.
  4. InvitedUsersTable.tsx:166 — fix empty search state message: Currently shows "No invited users yet" when search returns 0 — should say "No matching invited users".
  5. InvitedUsersTable.tsx:232 — add confirmation dialog for Revoke: Destructive action with no confirmation.
  6. Docstring coverage (17% → 80%): Add docstrings to all public functions in invited_user.py, user_admin_routes.py.
  7. Frontend test coverage: Add tests for InvitedUsersTable (search, pagination) and useAdminUsersPage hook.
  8. invited_user.py:49 — process-local semaphores: Note in code that these only work per-process and won't cap cross-worker concurrency. Consider documenting single-worker assumption or using Redis-based rate limiting.

Risk Assessment

Merge risk: MEDIUM | Rollback: EASY

The core changes are admin-only and additive — no existing user-facing flows are modified. Bulk invite scaling is safe (pre-check + unique constraint guard). Main risk is the serial Tally enrichment taking very long for large uploads (~50+ min for 5K users), but this runs in background and doesn't block the request. The merge conflict with #12407 on invited_user.py needs coordination.

REVIEW_COMPLETE
PR: #12471
Verdict: REQUEST_CHANGES
Blockers: 0

github-merge-queue bot pushed a commit that referenced this pull request Mar 19, 2026
## Summary

Reverts the invite system PRs due to security gaps identified during
review:

- The move from Supabase-native `allowed_users` gating to
application-level gating allows orphaned Supabase auth accounts (valid
JWT without a platform `User`)
- The auth middleware never verifies `User` existence, so orphaned users
get 500s instead of clean 403s
- OAuth/Google SSO signup completely bypasses the invite gate
- The DB trigger that atomically created `User` + `Profile` on signup
was dropped in favor of a client-initiated API call, introducing a
failure window

### Reverted PRs
- Reverts #12347 — Foundation: InvitedUser model, invite-gated signup,
admin UI
- Reverts #12374 — Tally enrichment: personalized prompts from form
submissions
- Reverts #12451 — Pre-check: POST /auth/check-invite endpoint
- Reverts #12452 (collateral) — Themed prompt categories /
SuggestionThemes UI. This PR built on top of #12374's
`suggested_prompts` backend field and `/chat/suggested-prompts`
endpoint, so it cannot remain without #12374. The copilot empty session
falls back to hardcoded default prompts.

### Migration
Includes a new migration (`20260319120000_revert_invite_system`) that:
- Drops the `InvitedUser` table and its enums (`InvitedUserStatus`,
`TallyComputationStatus`)
- Restores the `add_user_and_profile_to_platform()` trigger on
`auth.users`
- Backfills `User` + `Profile` rows for any auth accounts created during
the invite-gate window

### What's NOT reverted
- The `generate_username()` function (never dropped, still used by
backfill migration)
- The old `add_user_to_platform()` function (superseded by
`add_user_and_profile_to_platform()`)
- PR #12471 (admin UX improvements) — was never merged, no action needed

## Test plan
- [x] Verify migration: `InvitedUser` table dropped, enums dropped,
trigger restored
- [x] Verify backfill: no orphaned auth users, no users without Profile
- [x] Verify existing users can still log in (email + OAuth)
- [x] Verify CoPilot chat page loads with default prompts
- [ ] Verify new user signup creates `User` + `Profile` via the restored
trigger
- [ ] Verify admin `/admin/users` page loads without crashing
- [ ] Run backend tests: `poetry run test`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Zamil Majdy <zamil.majdy@agpt.co>
@github-actions github-actions bot added the conflicts Automatically applied to PRs with merge conflicts label Mar 19, 2026
@github-actions
Copy link
Contributor

This pull request has conflicts with the base branch, please resolve those so we can evaluate the pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflicts Automatically applied to PRs with merge conflicts platform/backend AutoGPT Platform - Back end platform/frontend AutoGPT Platform - Front end size/l size/m size/s size/xl

Projects

Status: 🚧 Needs work
Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants