Skip to content

refactor(platform): migrate Ayrshare to standard managed-credential flow - #12883

Merged
majdyz merged 34 commits into
devfrom
fix/copilot-sandbox-gh-bootstrap-and-ayrshare-prompt
Apr 24, 2026
Merged

refactor(platform): migrate Ayrshare to standard managed-credential flow#12883
majdyz merged 34 commits into
devfrom
fix/copilot-sandbox-gh-bootstrap-and-ayrshare-prompt

Conversation

@majdyz

@majdyz majdyz commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Why

Beta user report: AutoPilot told them to sign up for Ayrshare themselves — which AutoGPT actually manages — because AutoPilot inferred the requirement from the block description string rather than any structured schema. Root cause: Ayrshare was the only block family whose "credential" lived in a bespoke UserIntegrations.managed_credentials.ayrshare_profile_key side channel and whose blocks declared no credentials field. find_block / resolve_block_credentials had nothing to show the LLM, so the LLM guessed.

(An initial commit added a runtime gh CLI bootstrap for a separate "gh isn't installed in the sandbox" report — that work was empirically verified unnecessary and reverted; see the commit history for the bench results.)

What

Ayrshare now goes through the standard managed-credential flow:

  • New AyrshareManagedProvider alongside the existing AgentMailManagedProvider. Provisions the per-user profile as APIKeyCredentials(provider="ayrshare", is_managed=True) via the shared add_managed_credential path. Reuses any legacy managed_credentials.ayrshare_profile_key value on first provision so existing users keep their linked social accounts.
  • AyrshareManagedProvider.is_available() returns False so the ensure_managed_credentials startup sweep never auto-provisions Ayrshare (profile quota is a real per-user subscription cost). New public ensure_managed_credential(user_id, store, provider) helper lets the /api/integrations/ayrshare/sso_url route provision on demand, reusing the same distributed Redis lock + upsert path as AgentMail.
  • New ProviderBuilder.with_managed_api_key() method registers api_key as a supported auth type without the env-var-backed default credential that with_api_key() creates — so the org-level Ayrshare admin key cannot leak to blocks as a "profile key".
  • BaseAyrshareInput gains a shared credentials field; all 13 social blocks inherit it. Each run() now takes credentials: APIKeyCredentials; the inline get_profile_key guard + "please link a social account" error is gone. Standard resolve_block_credentials pre-run check owns the "not connected" path, returning a normal SetupRequirementsResponse.
  • Migration-ordering safety: post_provision hook on ManagedCredentialProvider clears the legacy ayrshare_profile_key field only after add_managed_credential has durably stored the managed credential. If persistence fails, the legacy key stays intact so a retry can reuse it — covered by TestMigrationOrderingSafety.
  • New public IntegrationCredentialsStore.get_user_integrations() — reads no longer have to reach past the _get_user_integrations privacy fence or abuse edit_user_integrations as a pseudo-read.
  • /api/integrations/ayrshare/sso_url collapses from a 60-line provision-then-sign dance to: pre-flight settings_available(), ensure_managed_credential, fetch the credential, sign a JWT.
  • IntegrationCredentialsStore.set_ayrshare_profile_key removed — the managed credential is now the only write path.
  • Legacy UserIntegrations.ManagedCredentials.ayrshare_profile_key field is retained so the managed provider can migrate existing users on first provision; removing the field is a follow-up once rollout has propagated.

How

After this PR, find_block returns Ayrshare blocks with a structured credentials_provider: ['ayrshare']. AutoPilot sees the credential requirement the same way it sees GitHub's or AgentMail's, calls run_block, and gets a plain SetupRequirementsResponse when the managed credential has not been provisioned yet. No more description-string speculation; the whole Ayrshare flow is the normal flow.

The Builder's AyrshareConnectButton (BlockType.AYRSHARE) still works — it hits the same endpoint, now a thin wrapper over the managed provider — so users still get the "Connect Social Accounts" popup for OAuth'ing individual social networks.

Test plan

  • poetry run pytest backend/blocks/test/test_block.py -k "ayrshare or PostTo" — 26/26 pass.
  • poetry run pytest backend/integrations/managed_providers/ayrshare_test.py — 10/10 pass.
  • poetry run pytest backend/api/features/integrations/router_test.py — 21/21 pass.
  • poetry run pyright on all touched backend files — 0 errors.
  • Runtime sanity: find_block on PostToXBlock lists credentials_provider: ['ayrshare'] in the JSON schema.
  • Manual QA in preview: connect social account via Builder's "Connect Social Accounts" button → post to X via CoPilot end-to-end.
  • Verify existing users with managed_credentials.ayrshare_profile_key continue to work without re-linking.

@majdyz
majdyz requested a review from a team as a code owner April 22, 2026 12:05
@majdyz
majdyz requested review from 0ubbe and kcze and removed request for a team April 22, 2026 12:05
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Apr 22, 2026
@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

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

Switches Ayrshare flows from per-user stored profile keys to on-demand managed credentials: adds AyrshareManagedProvider and provisioning, updates SSO endpoint to ensure managed credential and extract profile key from managed APIKeyCredentials, replaces user-id profile-key lookups with credentials-based inputs across Ayrshare blocks, removes legacy setter, and adds GH CLI sandbox bootstrap.

Changes

Cohort / File(s) Summary
API Endpoint
autogpt_platform/backend/backend/api/features/integrations/router.py
get_ayrshare_sso_url now checks Ayrshare settings, calls ensure_managed_credential(...), reads managed APIKeyCredentials via get_creds_by_provider(..., "ayrshare"), extracts api_key as profile key, and returns HTTP 502 on provisioning failure; logging/exception handling adjusted.
Provider config & util
autogpt_platform/backend/backend/blocks/ayrshare/_config.py, autogpt_platform/backend/backend/blocks/ayrshare/_util.py
Adds ayrshare provider via ProviderBuilder.with_managed_api_key(). Replaces async user-id profile-key lookup with get_profile_key(credentials: APIKeyCredentials) and embeds credential resolution into block input schema.
Posting blocks (many files)
autogpt_platform/backend/backend/blocks/ayrshare/post_to_*.py
All PostTo*Block.run signatures now accept credentials: APIKeyCredentials instead of user_id; removed prior async get_profile_key(user_id) lookups and early-return error flows; calls now pass profile_key=get_profile_key(credentials).
Managed provider & registration
autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py, .../managed_providers/__init__.py
New AyrshareManagedProvider (provider_name="ayrshare") with is_available() (returns False), provision() (migrates legacy key or calls AyrshareClient.create_profile() and returns APIKeyCredentials), deprovision() (no-op), migration helpers, and conditional registration in register_all().
Managed credentials core
autogpt_platform/backend/backend/integrations/managed_credentials.py
Refactored lock/provisioning into _provision_under_lock(...), added post_provision hook (default no-op), and introduced public ensure_managed_credential(user_id, store, provider) to permit on-demand provisioning (bypasses is_available() gate).
Credentials store cleanup
autogpt_platform/backend/backend/integrations/credentials_store.py
Removed legacy set_ayrshare_profile_key(user_id, profile_key) method and its SecretStr-wrapping persistence.
SDK builder
autogpt_platform/backend/backend/sdk/builder.py
Added ProviderBuilder.with_managed_api_key() to declare provider support for managed api_key auth without registering default credentials.
E2B sandbox bootstrap + tests
autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py, .../e2b_sandbox_test.py
Adds _bootstrap_sandbox() to best-effort install GitHub CLI during first-time sandbox creation (timeout-wrapped; warnings on failure) and tests that mock/assert bootstrap behavior.
Tests for Ayrshare provider
autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
New tests for _settings_available(), is_available(), legacy-key migration, provision() returning APIKeyCredentials, and migration ordering/regression scenarios.
OpenAPI
autogpt_platform/frontend/src/app/api/openapi.json
Updated description for GET /api/integrations/ayrshare/sso_url to document JWT-based SSO flow and that per-user profile keys are managed by AyrshareManagedProvider.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API as Integrations API
  participant Manager as ManagedCredsManager
  participant Store as CredStore
  participant Ayr as Ayrshare API

  Client->>API: GET /integrations/ayrshare/sso
  API->>Manager: ensure_managed_credential(user_id, provider="ayrshare")
  Manager->>Store: lock & has_managed_credential(user_id, "ayrshare")
  alt credential exists
    Store-->>Manager: APIKeyCredentials (managed)
  else
    Manager->>Manager: _provision_under_lock -> provider.provision(user_id)
    Manager->>Store: add_managed_credential(user_id, credential)
    Manager->>Manager: post_provision(user_id, store, credential)
  end
  Manager-->>API: APIKeyCredentials or failure
  API->>API: extract api_key secret -> profile_key
  API->>Client: 200 SSO URL (signed JWT with profile_key) / 502 on failure
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • 0ubbe
  • kcze
  • Pwuts

Poem

🐇 I hopped through keys and secret dens,
I swapped the old for managed pens,
Blocks now fetch creds in tidy rows,
Sandboxes fetch GH where cold wind blows,
A carrot nibble — migrations mend!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description comprehensively explains why the change was needed, what was implemented, and how it improves the system's credential handling and LLM inference capabilities.
Title check ✅ Passed The title accurately summarizes the main change: migrating Ayrshare from a custom credential flow to the standard managed-credential system used elsewhere in the platform.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/copilot-sandbox-gh-bootstrap-and-ayrshare-prompt

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

github-actions Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

🔍 PR Overlap Detection

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

🟢 Low Risk — File Overlap Only

These PRs touch the same files but different sections (click to expand)

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


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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py (1)

636-688: Solid coverage of the bootstrap contract.

Tests cover the happy path, exception swallowing, non-zero exit swallowing, single-invocation on create, and skip-on-reconnect. Good alignment with the best-effort design.

Optional: consider an additional assertion that the pinned _GH_CLI_VERSION (or the v{version} tag) actually appears in the emitted script — it would catch accidental bumps/typos in the f-string URL, which are otherwise only caught at runtime.

🧪 Optional assertion
         (cmd,), _ = sb.commands.run.call_args
         # The install script must at least detect gh and use the cli/cli repo.
         assert "command -v gh" in cmd
         assert "github.com/cli/cli" in cmd
+        from .e2b_sandbox import _GH_CLI_VERSION
+        assert f"/releases/download/v{_GH_CLI_VERSION}/" in cmd
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py` around
lines 636 - 688, Add an assertion in
TestBootstrapSandbox.test_bootstrap_runs_install_script to verify the pinned GH
CLI version appears in the generated install command: after extracting (cmd,), _
= sb.commands.run.call_args assert that either _GH_CLI_VERSION or the literal
f"v{_GH_CLI_VERSION}" (depending on how the URL is composed in
_bootstrap_sandbox) is present in cmd so accidental typos/bump in the f-string
URL are caught; reference symbols:
TestBootstrapSandbox.test_bootstrap_runs_install_script, _bootstrap_sandbox,
_GH_CLI_VERSION, and the local variable cmd.
autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py (1)

104-118: Remove redundant bash -c wrapper and shlex import in bootstrap invocation.

The e2b SDK's AsyncSandbox.commands.run() executes through a shell by default, so shell features like set -e, pipes, globs, and $(...) work without an explicit bash -c wrapper. Pass the script directly as _SANDBOX_BOOTSTRAP_SCRIPT (eliminating line 160's shlex.quote() wrapper) and remove the shlex import, which is only used here.

Also worth noting: if bootstrap fails (e.g., transient CDN error), _bootstrap_sandbox is only re-invoked on a fresh sandbox create, so gh remains missing for the life of that sandbox. That matches the documented best-effort contract.

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

In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py` around lines
104 - 118, The bootstrap script is being needlessly wrapped with shlex.quote and
a bash -c invocation; update the _bootstrap_sandbox call site to pass the
_SANDBOX_BOOTSTRAP_SCRIPT string directly to AsyncSandbox.commands.run (or
whatever invocation uses the script) so the shell runs it natively, and remove
the now-unused shlex import from the file; ensure references to
_SANDBOX_BOOTSTRAP_SCRIPT and _bootstrap_sandbox remain intact and that no extra
quoting/wrapping is applied.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py`:
- Around line 49-57: provision currently returns a new APIKeyCredentials but
does not clear the legacy ayrshare_profile_key used by
_get_or_create_profile_key, so the legacy branch keeps winning; after creating
the APIKeyCredentials in provision, clear
UserIntegrations.managed_credentials.ayrshare_profile_key (e.g., set it to
None/remove it) and persist the change via your user integrations save/update
method so the legacy field is actually removed and subsequent calls use the
managed credential path instead of the legacy key.

---

Nitpick comments:
In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py`:
- Around line 636-688: Add an assertion in
TestBootstrapSandbox.test_bootstrap_runs_install_script to verify the pinned GH
CLI version appears in the generated install command: after extracting (cmd,), _
= sb.commands.run.call_args assert that either _GH_CLI_VERSION or the literal
f"v{_GH_CLI_VERSION}" (depending on how the URL is composed in
_bootstrap_sandbox) is present in cmd so accidental typos/bump in the f-string
URL are caught; reference symbols:
TestBootstrapSandbox.test_bootstrap_runs_install_script, _bootstrap_sandbox,
_GH_CLI_VERSION, and the local variable cmd.

In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py`:
- Around line 104-118: The bootstrap script is being needlessly wrapped with
shlex.quote and a bash -c invocation; update the _bootstrap_sandbox call site to
pass the _SANDBOX_BOOTSTRAP_SCRIPT string directly to AsyncSandbox.commands.run
(or whatever invocation uses the script) so the shell runs it natively, and
remove the now-unused shlex import from the file; ensure references to
_SANDBOX_BOOTSTRAP_SCRIPT and _bootstrap_sandbox remain intact and that no extra
quoting/wrapping is applied.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f8746368-7fcd-4e24-981f-5a164af366b6

📥 Commits

Reviewing files that changed from the base of the PR and between b98bcf3 and c7aff39.

📒 Files selected for processing (21)
  • autogpt_platform/backend/backend/api/features/integrations/router.py
  • autogpt_platform/backend/backend/blocks/ayrshare/_config.py
  • autogpt_platform/backend/backend/blocks/ayrshare/_util.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_bluesky.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_facebook.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_gmb.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_instagram.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_linkedin.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_reddit.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_snapchat.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_telegram.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_threads.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_tiktok.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_x.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_youtube.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/integrations/credentials_store.py
  • autogpt_platform/backend/backend/integrations/managed_providers/__init__.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
💤 Files with no reviewable changes (1)
  • autogpt_platform/backend/backend/integrations/credentials_store.py

Comment thread autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py Outdated
Comment thread autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py Outdated
Comment thread autogpt_platform/backend/backend/blocks/ayrshare/_config.py Outdated
Comment thread autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py Outdated
Comment thread autogpt_platform/backend/backend/blocks/ayrshare/_util.py
@codecov

codecov Bot commented Apr 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.05128% with 56 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.26%. Comparing base (10e421c) to head (bfc3a69).
⚠️ Report is 3 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev   #12883      +/-   ##
==========================================
+ Coverage   68.00%   69.26%   +1.26%     
==========================================
  Files        1913     1880      -33     
  Lines      148717   144517    -4200     
  Branches    15504    14836     -668     
==========================================
- Hits       101129   100101    -1028     
+ Misses      44573    41444    -3129     
+ Partials     3015     2972      -43     
Flag Coverage Δ
autogpt-agent ?
platform-backend 77.76% <81.69%> (+0.04%) ⬆️
platform-frontend 25.09% <100.00%> (+0.18%) ⬆️
platform-frontend-e2e 29.95% <100.00%> (-0.37%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Platform Backend 77.76% <81.69%> (+0.04%) ⬆️
Platform Frontend 32.04% <100.00%> (+0.02%) ⬆️
AutoGPT Libs ∅ <ø> (∅)
Classic AutoGPT ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread autogpt_platform/backend/backend/blocks/ayrshare/_util.py
Comment thread autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@autogpt_platform/backend/backend/integrations/managed_credentials.py`:
- Around line 132-137: The migration currently runs provider.provision (which
may clear the legacy ayrshare_profile_key) before persisting, risking loss if
store.add_managed_credential fails; change the flow in the async with
locks.locked(key) block so you first call provider.provision(user_id) but do not
allow it to clear the legacy Ayrshare key, or better: persist the returned
credential with store.add_managed_credential(user_id, credential) and only after
that succeeds clear the legacy ayrshare_profile_key (or perform both
persist+clear in a single durable transaction/post-persist hook). Update code
paths around locks.locked, store.has_managed_credential, provider.provision, and
store.add_managed_credential to ensure clearing of ayrshare_profile_key happens
after successful add, and add a unit/integration test that simulates
store.add_managed_credential raising/cancelling after provider.provision to
verify the legacy key is retained and the migration is idempotent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 44208509-0ea4-4f17-867e-5e76d69994b7

📥 Commits

Reviewing files that changed from the base of the PR and between d40410e and b687cbd.

📒 Files selected for processing (4)
  • autogpt_platform/backend/backend/api/features/integrations/router.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.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). (9)
  • GitHub Check: check API types
  • GitHub Check: end-to-end tests
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: type-check (3.11)
  • GitHub Check: Seer Code Review
  • GitHub Check: Analyze (python)
  • GitHub Check: Check PR Status
🧰 Additional context used
📓 Path-based instructions (5)
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

autogpt_platform/backend/**/*.py: Use poetry run ... command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies like openpyxl
Use absolute imports with from backend.module import ... for cross-package imports; single-dot relative imports are acceptable for sibling modules within the same package; avoid double-dot relative imports
Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead
Use Pydantic models over dataclass/namedtuple/dict for structured data
Do not use linter suppressors — no # type: ignore, # noqa, # pyright: ignore; fix the type/code instead
Prefer list comprehensions over manual loop-and-append patterns
Use early return with guard clauses first to avoid deep nesting
Use %s for deferred interpolation in debug log statements for efficiency; use f-strings elsewhere for readability (e.g., logger.debug("Processing %s items", count) vs logger.info(f"Processing {count} items"))
Sanitize error paths by using os.path.basename() in error messages to avoid leaking directory structure
Be aware of TOCTOU (Time-Of-Check-Time-Of-Use) issues — avoid check-then-act patterns for file access and credit charging
Use transaction=True for Redis pipelines to ensure atomicity on multi-step operations
Use max(0, value) guards for computed values that should never be negative
Keep files under ~300 lines; if a file grows beyond this, split by responsibility (extract helpers, models, or a sub-module into a new file)
Keep functions under ~40 lines; extract named helpers when a function grows longer
...

Files:

  • autogpt_platform/backend/backend/api/features/integrations/router.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
autogpt_platform/backend/backend/api/features/**/*.py

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

Update routes in '/backend/backend/api/features/' and add/update Pydantic models in the same directory for API development

Files:

  • autogpt_platform/backend/backend/api/features/integrations/router.py
autogpt_platform/{backend,autogpt_libs}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/api/features/integrations/router.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
autogpt_platform/backend/**/api/**/*.py

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

autogpt_platform/backend/**/api/**/*.py: Use Security() instead of Depends() for authentication dependencies to get proper OpenAPI security specification
Follow SSE (Server-Sent Events) protocol: use data: lines for frontend-parsed events (must match Zod schema) and : comment lines for heartbeats/status

Files:

  • autogpt_platform/backend/backend/api/features/integrations/router.py
autogpt_platform/backend/**/*_test.py

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

autogpt_platform/backend/**/*_test.py: Use pytest with snapshot testing for API responses
Colocate test files with source files using *_test.py naming convention
Mock at boundaries — mock where the symbol is used, not where it's defined; after refactoring, update mock targets to match new module paths
Use AsyncMock from unittest.mock for async functions in tests
When writing tests, use Test-Driven Development (TDD): write failing tests marked with @pytest.mark.xfail before implementation, then remove the marker once the implementation is complete
When creating snapshots in tests, use poetry run pytest path/to/test.py --snapshot-update; always review snapshot changes with git diff before committing

Files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
🧠 Learnings (12)
📓 Common learnings
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12426
File: autogpt_platform/backend/backend/copilot/sdk/service.py:0-0
Timestamp: 2026-03-15T16:52:15.463Z
Learning: In Significant-Gravitas/AutoGPT (copilot backend), GitHub tokens (GH_TOKEN / GITHUB_TOKEN) for the `gh` CLI are injected lazily per-command in `autogpt_platform/backend/backend/copilot/tools/bash_exec._execute_on_e2b()` by calling `integration_creds.get_integration_env_vars(user_id)`, not on the global SDK subprocess environment in `sdk/service.py`. This scopes credentials to individual E2B sandbox command invocations and prevents token leakage into tool output streams or uploaded transcripts.
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12536
File: autogpt_platform/frontend/src/app/api/openapi.json:5770-5790
Timestamp: 2026-03-24T21:25:15.983Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12536`
File: autogpt_platform/frontend/src/app/api/openapi.json
Learning: The OpenAPI spec file is auto-generated; per established convention, endpoints generally declare only 200/201, 401, and 422 responses. Do not suggest adding explicit 403/404 response entries for single operations unless planning a repo-wide spec update. Prefer clarifying such behaviors in endpoint descriptions/docstrings instead of altering response maps.
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.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12566
File: autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts:968-974
Timestamp: 2026-03-26T00:32:06.673Z
Learning: In Significant-Gravitas/AutoGPT, the admin-facing methods in `autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts` (e.g., `addUserCredits`, `getUsersHistory`, `getUserRateLimit`, `resetUserRateLimit`) intentionally follow the legacy `BackendAPI` pattern with manually defined types in `autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts`. Migrating these admin endpoints to the generated OpenAPI hooks (`@/app/api/__generated__/endpoints/`) is a planned separate effort covering all admin endpoints together, not done piecemeal per PR. Do not flag individual admin type additions in `types.ts` as blocking issues.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12213
File: autogpt_platform/frontend/src/app/api/openapi.json:10030-10037
Timestamp: 2026-03-01T07:59:02.311Z
Learning: Repo: Significant-Gravitas/AutoGPT PR: 12213 — For MCP manual token storage, backend model autogpt_platform/backend/backend/api/features/mcp/routes.py defines MCPStoreTokenRequest.token as Pydantic SecretStr with a min length constraint, which generates OpenAPI schema metadata (format: "password", writeOnly: true, minLength: 1) in autogpt_platform/frontend/src/app/api/openapi.json. Prefer SecretStr (with length constraints) for sensitive request fields so generated TS clients and docs treat them as secrets.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12774
File: autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py:0-0
Timestamp: 2026-04-14T06:34:02.835Z
Learning: In `autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py`, the `asyncio.wait_for()` retry loop around `AsyncSandbox.create()` (introduced in PR `#12774`) can leak up to `_SANDBOX_CREATE_MAX_RETRIES - 1` (≤2) orphaned E2B sandboxes per hang incident because `wait_for` cancels only the client-side wait while E2B may complete server-side provisioning. With the default `on_timeout="pause"` lifecycle, leaked orphaned sandboxes are **paused** (not killed) when their original `end_at` is reached and persist indefinitely until explicitly killed — there is NO automatic E2B project-level cleanup. Operators must manage these manually or via their own cleanup jobs. The sandbox_id is not accessible from the timed-out coroutine, so recovery via `AsyncSandbox.connect(sandbox_id)` is not possible at timeout. This is an intentionally accepted trade-off; a proper fix is deferred to a follow-up PR. Do NOT flag the retry loop as a blocking issue.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/copilot/workflow_import/converter.py:0-0
Timestamp: 2026-03-17T10:57:12.953Z
Learning: In Significant-Gravitas/AutoGPT PR `#12440`, `autogpt_platform/backend/backend/copilot/workflow_import/converter.py` was fully rewritten (commit 732960e2d) to no longer make direct LLM/OpenAI API calls. The converter now builds a structured text prompt for AutoPilot/CoPilot instead. There is no `response.choices` access or any direct LLM client usage in this file. Do not flag `response.choices` access or LLM client initialization patterns as issues in this file.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12879
File: autogpt_platform/frontend/src/app/api/openapi.json:14576-14577
Timestamp: 2026-04-22T05:58:28.595Z
Learning: Repo: Significant-Gravitas/AutoGPT — autogpt_platform
Process convention: When adding new CoPilot tool response models and updating ToolResponseUnion in backend/api/features/chat/routes.py, regenerate the frontend OpenAPI schema via `poetry run export-api-schema` (do not hand-edit autogpt_platform/frontend/src/app/api/openapi.json).
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12213
File: autogpt_platform/frontend/src/app/api/openapi.json:9983-9995
Timestamp: 2026-02-27T15:59:00.370Z
Learning: Repo: Significant-Gravitas/AutoGPT PR: 12213 — Backend/frontend OpenAPI codegen
Learning: For MCP schema models, required OpenAPI fields must have no defaults in Pydantic. Specifically, MCPToolInfo.input_schema must be required (no Field(default_factory=dict)) so openapi.json emits it in "required", ensuring generated TS types treat input_schema as non-optional.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12356
File: autogpt_platform/backend/backend/copilot/constants.py:9-12
Timestamp: 2026-03-10T08:39:22.025Z
Learning: In Significant-Gravitas/AutoGPT PR `#12356`, the `COPILOT_SYNTHETIC_ID_PREFIX = "copilot-"` check in `create_auto_approval_record` (human_review.py) is intentional and safe. The `graph_exec_id` passed to this function comes from server-side `PendingHumanReview` DB records (not from user input); the API only accepts `node_exec_id` from users. Synthetic `copilot-*` IDs are only ever created server-side in `run_block.py`. The prefix skip avoids a DB lookup for a `AgentGraphExecution` record that legitimately does not exist for CoPilot sessions, while `user_id` scoping is enforced at the auth layer and on the resulting auto-approval record.
📚 Learning: 2026-03-07T07:43:15.754Z
Learnt from: kcze
Repo: Significant-Gravitas/AutoGPT PR: 12328
File: autogpt_platform/frontend/src/app/api/openapi.json:1116-1118
Timestamp: 2026-03-07T07:43:15.754Z
Learning: In Significant-Gravitas/AutoGPT, v2 chat endpoints often declare HTTPBearerJWT at the router level while using Depends(auth.get_user_id) that returns None for unauthenticated users; effective behavior is optional auth. Keep this convention unless doing a repo-wide OpenAPI update; prefer clarifying descriptions over per-operation security changes.

Applied to files:

  • autogpt_platform/backend/backend/api/features/integrations/router.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/api/features/integrations/router.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.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/api/features/integrations/router.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.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/api/features/integrations/router.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
📚 Learning: 2026-03-31T15:37:38.626Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/tools/agent_generator/fixer.py:37-47
Timestamp: 2026-03-31T15:37:38.626Z
Learning: When validating/constructing Anthropic API model IDs in Significant-Gravitas/AutoGPT, allow the hyphen-separated Claude Opus 4.6 model ID `claude-opus-4-6` (it corresponds to `LlmModel.CLAUDE_4_6_OPUS` in `autogpt_platform/backend/backend/blocks/llm.py`). Do NOT require the dot-separated form in Anthropic contexts. Only OpenRouter routing variants should use the dot separator (e.g., `anthropic/claude-opus-4.6`); `claude-opus-4-6` should be treated as correct when passed to Anthropic, and flagged only if it’s used in the OpenRouter path where the dot form is expected.

Applied to files:

  • autogpt_platform/backend/backend/api/features/integrations/router.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
📚 Learning: 2026-04-15T02:43:36.890Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12780
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-15T02:43:36.890Z
Learning: When reviewing Python exception handlers, do not flag `isinstance(e, X)` checks as dead/unreachable if the caught exception `X` is a subclass of the exception type being handled. For example, if `X` (e.g., `VirusScanError`) inherits from `ValueError` (directly or via an intermediate class) and it can be raised within an `except ValueError:` block, then `isinstance(e, X)` inside that handler is reachable and should not be treated as dead code.

Applied to files:

  • autogpt_platform/backend/backend/api/features/integrations/router.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
📚 Learning: 2026-04-22T11:46:04.431Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/config.py:0-0
Timestamp: 2026-04-22T11:46:04.431Z
Learning: Do not flag the Claude Sonnet 4.6 model ID as incorrect when it uses the project’s established hyphenated convention: `anthropic/claude-sonnet-4-6`. This hyphen form is the intentional, production convention and should be treated as valid (including in files like llm.py, blocks tests, reasoning.py, `_is_anthropic_model` tests, and config defaults). Note that OpenRouter also accepts the dot variant `anthropic/claude-sonnet-4.6`, so either form may be tolerated, but `anthropic/claude-sonnet-4-6` should be considered the standard to match project usage.

Applied to files:

  • autogpt_platform/backend/backend/api/features/integrations/router.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
📚 Learning: 2026-04-22T11:46:12.892Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/baseline/service.py:322-332
Timestamp: 2026-04-22T11:46:12.892Z
Learning: In this codebase (Significant-Gravitas/AutoGPT), OpenRouter-routed Anthropic model IDs should use the hyphen-separated convention (e.g., `anthropic/claude-sonnet-4-6`, `anthropic/claude-opus-4-6`). Although OpenRouter may accept both hyphen and dot variants, treat the hyphen-separated form as the intended, correct codebase-wide convention and do not flag it as an error. Only flag the dot-separated variant (e.g., `anthropic/claude-sonnet-4.6`) as incorrect when reviewing/validating model ID strings for OpenRouter-routed Anthropic models.

Applied to files:

  • autogpt_platform/backend/backend/api/features/integrations/router.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
📚 Learning: 2026-04-03T13:50:10.521Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12206
File: autogpt_platform/backend/backend/api/external/v2/integrations/helpers.py:25-46
Timestamp: 2026-04-03T13:50:10.521Z
Learning: In `autogpt_platform/backend/backend/api/external/v2/integrations/helpers.py`, `CredentialInfo.from_internal` is intentionally a read-only external API view that exposes only: id, type, provider, title, scopes, expires_at. It omits internal metadata and secret fields by design. Do not flag omitted fields in CredentialInfo as missing information — the limited field set is the correct external API contract.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_credentials.py
📚 Learning: 2026-01-19T07:20:23.494Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11795
File: autogpt_platform/backend/backend/api/features/chat/tools/utils.py:92-111
Timestamp: 2026-01-19T07:20:23.494Z
Learning: In autogpt_platform/backend/backend/api/features/chat/tools/utils.py, the _serialize_missing_credential function uses next(iter(field_info.provider)) for provider selection. The PR author confirmed this non-deterministic provider selection is acceptable because the function returns both "type" (single, for backward compatibility) and "types" (full array), which achieves the primary goal of deterministic credential type presentation.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_credentials.py
📚 Learning: 2026-04-08T17:28:23.439Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/AGENTS.md:0-0
Timestamp: 2026-04-08T17:28:23.439Z
Learning: Applies to autogpt_platform/backend/**/*_test.py : When creating snapshots in tests, use `poetry run pytest path/to/test.py --snapshot-update`; always review snapshot changes with `git diff` before committing

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py

@github-actions github-actions Bot added the platform/frontend AutoGPT Platform - Front end label Apr 22, 2026
Comment thread autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py Outdated
Comment thread autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py Outdated
Comment thread autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py Outdated
Comment thread autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py (1)

128-135: Drop the isinstance fallback — legacy_key is typed Optional[SecretStr].

UserIntegrations.ManagedCredentials.ayrshare_profile_key: Optional[SecretStr] (see backend/data/model.py), and the if legacy_key: guard already filters None, so the else str(legacy_key) branch is unreachable defensive code. As per coding guidelines ("Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead"), prefer calling the method directly on the declared type.

♻️ Proposed simplification
     user_integrations = await store._get_user_integrations(user_id)
     legacy_key = user_integrations.managed_credentials.ayrshare_profile_key
     if legacy_key:
         logger.debug("[ayrshare] Reusing legacy profile key for user %s", user_id)
-        return (
-            legacy_key.get_secret_value()
-            if isinstance(legacy_key, SecretStr)
-            else str(legacy_key)
-        )
+        return legacy_key.get_secret_value()

As per coding guidelines: "Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead".

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

In `@autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py`
around lines 128 - 135, legacy_key is declared as Optional[SecretStr]
(user_integrations.managed_credentials.ayrshare_profile_key) and the preceding
if legacy_key: already filters None, so remove the isinstance fallback and call
legacy_key.get_secret_value() directly; update the block around legacy_key and
the return in the function handling Ayrshare profile keys to simply log the
reuse and return legacy_key.get_secret_value().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py`:
- Around line 20-24: Update the module docstring to remove the statement that
legacy migration "migrate ... and clear the legacy field in the same write" and
instead describe the current ordering: first persist the new managed credential
via add_managed_credential, then clear
UserIntegrations.managed_credentials.ayrshare_profile_key in a separate
edit_user_integrations write during post_provision; mention that this split is
intentional and covered by TestMigrationOrderingSafety to prevent data-loss.

---

Nitpick comments:
In `@autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py`:
- Around line 128-135: legacy_key is declared as Optional[SecretStr]
(user_integrations.managed_credentials.ayrshare_profile_key) and the preceding
if legacy_key: already filters None, so remove the isinstance fallback and call
legacy_key.get_secret_value() directly; update the block around legacy_key and
the return in the function handling Ayrshare profile keys to simply log the
reuse and return legacy_key.get_secret_value().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 554246aa-01cf-4c1d-80d0-5c18070dcb74

📥 Commits

Reviewing files that changed from the base of the PR and between b687cbd and d9dcd52.

📒 Files selected for processing (7)
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_facebook.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_instagram.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
  • autogpt_platform/frontend/src/app/api/openapi.json
✅ Files skipped from review due to trivial changes (1)
  • autogpt_platform/frontend/src/app/api/openapi.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_instagram.py
  • autogpt_platform/backend/backend/integrations/managed_credentials.py
  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_facebook.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). (14)
  • GitHub Check: check API types
  • GitHub Check: integration_test
  • GitHub Check: test (3.11)
  • GitHub Check: type-check (3.13)
  • GitHub Check: type-check (3.12)
  • GitHub Check: test (3.12)
  • GitHub Check: lint
  • GitHub Check: test (3.13)
  • GitHub Check: type-check (3.11)
  • GitHub Check: end-to-end tests
  • GitHub Check: Seer Code Review
  • GitHub Check: Check PR Status
  • GitHub Check: Analyze (typescript)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
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

autogpt_platform/backend/**/*.py: Use poetry run ... command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies like openpyxl
Use absolute imports with from backend.module import ... for cross-package imports; single-dot relative imports are acceptable for sibling modules within the same package; avoid double-dot relative imports
Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead
Use Pydantic models over dataclass/namedtuple/dict for structured data
Do not use linter suppressors — no # type: ignore, # noqa, # pyright: ignore; fix the type/code instead
Prefer list comprehensions over manual loop-and-append patterns
Use early return with guard clauses first to avoid deep nesting
Use %s for deferred interpolation in debug log statements for efficiency; use f-strings elsewhere for readability (e.g., logger.debug("Processing %s items", count) vs logger.info(f"Processing {count} items"))
Sanitize error paths by using os.path.basename() in error messages to avoid leaking directory structure
Be aware of TOCTOU (Time-Of-Check-Time-Of-Use) issues — avoid check-then-act patterns for file access and credit charging
Use transaction=True for Redis pipelines to ensure atomicity on multi-step operations
Use max(0, value) guards for computed values that should never be negative
Keep files under ~300 lines; if a file grows beyond this, split by responsibility (extract helpers, models, or a sub-module into a new file)
Keep functions under ~40 lines; extract named helpers when a function grows longer
...

Files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
autogpt_platform/backend/backend/blocks/**/*.py

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

autogpt_platform/backend/backend/blocks/**/*.py: Inherit from 'Block' base class with input/output schemas when adding new blocks in backend
Implement 'run' method with proper error handling in backend blocks
Generate block UUID using 'uuid.uuid4()' when creating new blocks in backend
Write tests alongside block implementation when adding new blocks in backend

autogpt_platform/backend/backend/blocks/**/*.py: For blocks handling files, use store_media_file() with return_format="for_local_processing" when processing with local tools (ffmpeg, MoviePy, PIL)
For blocks handling files, use store_media_file() with return_format="for_external_api" when sending content to external APIs (Replicate, OpenAI)
For blocks returning files, use store_media_file() with return_format="for_block_output" to enable auto-adaptation to execution context (workspace:// in CoPilot, data URI in graphs)
When creating new blocks, inherit from Block base class, define input/output schemas using BlockSchema, implement async run method, and generate unique block ID using uuid.uuid4()

Files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
autogpt_platform/{backend,autogpt_libs}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
autogpt_platform/backend/**/*_test.py

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

autogpt_platform/backend/**/*_test.py: Use pytest with snapshot testing for API responses
Colocate test files with source files using *_test.py naming convention
Mock at boundaries — mock where the symbol is used, not where it's defined; after refactoring, update mock targets to match new module paths
Use AsyncMock from unittest.mock for async functions in tests
When writing tests, use Test-Driven Development (TDD): write failing tests marked with @pytest.mark.xfail before implementation, then remove the marker once the implementation is complete
When creating snapshots in tests, use poetry run pytest path/to/test.py --snapshot-update; always review snapshot changes with git diff before committing

Files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
🧠 Learnings (40)
📓 Common learnings
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12426
File: autogpt_platform/backend/backend/copilot/sdk/service.py:0-0
Timestamp: 2026-03-15T16:52:15.463Z
Learning: In Significant-Gravitas/AutoGPT (copilot backend), GitHub tokens (GH_TOKEN / GITHUB_TOKEN) for the `gh` CLI are injected lazily per-command in `autogpt_platform/backend/backend/copilot/tools/bash_exec._execute_on_e2b()` by calling `integration_creds.get_integration_env_vars(user_id)`, not on the global SDK subprocess environment in `sdk/service.py`. This scopes credentials to individual E2B sandbox command invocations and prevents token leakage into tool output streams or uploaded transcripts.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12774
File: autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py:0-0
Timestamp: 2026-04-14T06:34:02.835Z
Learning: In `autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py`, the `asyncio.wait_for()` retry loop around `AsyncSandbox.create()` (introduced in PR `#12774`) can leak up to `_SANDBOX_CREATE_MAX_RETRIES - 1` (≤2) orphaned E2B sandboxes per hang incident because `wait_for` cancels only the client-side wait while E2B may complete server-side provisioning. With the default `on_timeout="pause"` lifecycle, leaked orphaned sandboxes are **paused** (not killed) when their original `end_at` is reached and persist indefinitely until explicitly killed — there is NO automatic E2B project-level cleanup. Operators must manage these manually or via their own cleanup jobs. The sandbox_id is not accessible from the timed-out coroutine, so recovery via `AsyncSandbox.connect(sandbox_id)` is not possible at timeout. This is an intentionally accepted trade-off; a proper fix is deferred to a follow-up PR. Do NOT flag the retry loop as a blocking issue.
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12536
File: autogpt_platform/frontend/src/app/api/openapi.json:5770-5790
Timestamp: 2026-03-24T21:25:15.983Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12536`
File: autogpt_platform/frontend/src/app/api/openapi.json
Learning: The OpenAPI spec file is auto-generated; per established convention, endpoints generally declare only 200/201, 401, and 422 responses. Do not suggest adding explicit 403/404 response entries for single operations unless planning a repo-wide spec update. Prefer clarifying such behaviors in endpoint descriptions/docstrings instead of altering response maps.
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.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/backend/backend/copilot/pending_messages.py:52-64
Timestamp: 2026-04-14T14:36:25.545Z
Learning: In `autogpt_platform/backend/backend/copilot` (PR `#12773`, commit d7bced0c6): when draining pending messages into `session.messages`, each message's text is sanitized via `strip_user_context_tags` before persistence to prevent user-controlled `<user_context>` injection from bypassing the trusted server-side context prefix. Additionally, if `upsert_chat_session` fails after draining, the drained `PendingMessage` objects are requeued back to Redis to avoid silent message loss. Do NOT flag the drain-then-requeue pattern as redundant — it is the intentional failure-resilience strategy for the pending buffer.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12356
File: autogpt_platform/backend/backend/copilot/constants.py:9-12
Timestamp: 2026-03-10T08:39:22.025Z
Learning: In Significant-Gravitas/AutoGPT PR `#12356`, the `COPILOT_SYNTHETIC_ID_PREFIX = "copilot-"` check in `create_auto_approval_record` (human_review.py) is intentional and safe. The `graph_exec_id` passed to this function comes from server-side `PendingHumanReview` DB records (not from user input); the API only accepts `node_exec_id` from users. Synthetic `copilot-*` IDs are only ever created server-side in `run_block.py`. The prefix skip avoids a DB lookup for a `AgentGraphExecution` record that legitimately does not exist for CoPilot sessions, while `user_id` scoping is enforced at the auth layer and on the resulting auto-approval record.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12213
File: autogpt_platform/frontend/src/app/api/openapi.json:10030-10037
Timestamp: 2026-03-01T07:59:02.311Z
Learning: Repo: Significant-Gravitas/AutoGPT PR: 12213 — For MCP manual token storage, backend model autogpt_platform/backend/backend/api/features/mcp/routes.py defines MCPStoreTokenRequest.token as Pydantic SecretStr with a min length constraint, which generates OpenAPI schema metadata (format: "password", writeOnly: true, minLength: 1) in autogpt_platform/frontend/src/app/api/openapi.json. Prefer SecretStr (with length constraints) for sensitive request fields so generated TS clients and docs treat them as secrets.
📚 Learning: 2026-02-05T04:11:00.596Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11796
File: autogpt_platform/backend/backend/blocks/video/concat.py:3-4
Timestamp: 2026-02-05T04:11:00.596Z
Learning: In autogpt_platform/backend/backend/blocks/**/*.py, when creating a new block, generate a UUID once with uuid.uuid4() and hard-code the resulting string as the block's id parameter. Do not call uuid.uuid4() at runtime; IDs must be constant across all imports and runs to ensure stability.

Applied to files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
📚 Learning: 2026-03-16T16:32:21.686Z
Learnt from: Abhi1992002
Repo: Significant-Gravitas/AutoGPT PR: 12417
File: autogpt_platform/backend/backend/blocks/agent_mail/pods.py:62-74
Timestamp: 2026-03-16T16:32:21.686Z
Learning: In autogpt_platform/backend/backend/blocks/, the Block base class execute() already wraps run() in a try/except to convert uncaught exceptions into BlockExecutionError/BlockUnknownError. Do not add per-block try/except in individual block run() methods, as this is not the established pattern (e.g., Gmail, Slack, Todoist blocks omit it). Only use explicit try/except within blocks that need to distinguish between success and error yield paths inside a generator (e.g., attachment blocks). This guidance applies to all Python files under autogpt_platform/backend/backend/blocks/ and similar block implementations; avoid duplicating error handling in run() unless a block requires generator-based branching.

Applied to files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.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/blocks/ayrshare/post_to_pinterest.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.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/blocks/ayrshare/post_to_pinterest.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
📚 Learning: 2026-03-16T16:30:11.452Z
Learnt from: Abhi1992002
Repo: Significant-Gravitas/AutoGPT PR: 12417
File: autogpt_platform/backend/backend/blocks/agent_mail/threads.py:80-102
Timestamp: 2026-03-16T16:30:11.452Z
Learning: In autogpt_platform/backend/backend/blocks/ (and related blocks under autogpt_platform/backend/backend/blocks/), do not add try/except blocks around a block's run() method for standard error propagation. The block executor framework (backend/executor/manager.py) catches uncaught exceptions from run() and emits them on the 'error' output. Only add explicit try/except blocks when you need to control partial outputs in failure cases (e.g., certain outputs must not be yielded on error, as in attachment blocks). This is the standard pattern across the codebase; apply it broadly to blocks' run() implementations.

Applied to files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
📚 Learning: 2026-03-16T16:30:23.196Z
Learnt from: Abhi1992002
Repo: Significant-Gravitas/AutoGPT PR: 12417
File: autogpt_platform/backend/backend/blocks/agent_mail/pods.py:62-74
Timestamp: 2026-03-16T16:30:23.196Z
Learning: In any Python file under autogpt_platform/backend/backend/blocks, do not add a try/except around run() solely for standard error handling. The block framework’s _execute() in _base.py already catches unhandled exceptions and re-raises as BlockExecutionError or BlockUnknownError. If you yield ("error", message), _execute() raises BlockExecutionError immediately, so the error port will not propagate downstream. Reserve explicit try/except for scenarios where you must control partial output (e.g., attachment blocks that must skip yielding content_base64 on failure).

Applied to files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
📚 Learning: 2026-03-16T16:30:11.452Z
Learnt from: Abhi1992002
Repo: Significant-Gravitas/AutoGPT PR: 12417
File: autogpt_platform/backend/backend/blocks/agent_mail/threads.py:80-102
Timestamp: 2026-03-16T16:30:11.452Z
Learning: Do not wrap synchronous AgentMail SDK calls with asyncio.to_thread() in blocks under autogpt_platform/backend/backend/blocks (and across the codebase). The block executor runs node execution in dedicated threads via asyncio.run_coroutine_threadsafe (see manager.py around lines ~745-752 and ~1079). The existing pattern avoids using asyncio.to_thread for SDK calls inside async run() methods, so maintain that approach and do not add to_thread usage in these code paths.

Applied to files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.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/blocks/ayrshare/post_to_pinterest.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
📚 Learning: 2026-03-31T15:37:38.626Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/tools/agent_generator/fixer.py:37-47
Timestamp: 2026-03-31T15:37:38.626Z
Learning: When validating/constructing Anthropic API model IDs in Significant-Gravitas/AutoGPT, allow the hyphen-separated Claude Opus 4.6 model ID `claude-opus-4-6` (it corresponds to `LlmModel.CLAUDE_4_6_OPUS` in `autogpt_platform/backend/backend/blocks/llm.py`). Do NOT require the dot-separated form in Anthropic contexts. Only OpenRouter routing variants should use the dot separator (e.g., `anthropic/claude-opus-4.6`); `claude-opus-4-6` should be treated as correct when passed to Anthropic, and flagged only if it’s used in the OpenRouter path where the dot form is expected.

Applied to files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
📚 Learning: 2026-04-15T02:43:36.890Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12780
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-15T02:43:36.890Z
Learning: When reviewing Python exception handlers, do not flag `isinstance(e, X)` checks as dead/unreachable if the caught exception `X` is a subclass of the exception type being handled. For example, if `X` (e.g., `VirusScanError`) inherits from `ValueError` (directly or via an intermediate class) and it can be raised within an `except ValueError:` block, then `isinstance(e, X)` inside that handler is reachable and should not be treated as dead code.

Applied to files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
📚 Learning: 2026-04-22T11:46:04.431Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/config.py:0-0
Timestamp: 2026-04-22T11:46:04.431Z
Learning: Do not flag the Claude Sonnet 4.6 model ID as incorrect when it uses the project’s established hyphenated convention: `anthropic/claude-sonnet-4-6`. This hyphen form is the intentional, production convention and should be treated as valid (including in files like llm.py, blocks tests, reasoning.py, `_is_anthropic_model` tests, and config defaults). Note that OpenRouter also accepts the dot variant `anthropic/claude-sonnet-4.6`, so either form may be tolerated, but `anthropic/claude-sonnet-4-6` should be considered the standard to match project usage.

Applied to files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
📚 Learning: 2026-04-22T11:46:12.892Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/baseline/service.py:322-332
Timestamp: 2026-04-22T11:46:12.892Z
Learning: In this codebase (Significant-Gravitas/AutoGPT), OpenRouter-routed Anthropic model IDs should use the hyphen-separated convention (e.g., `anthropic/claude-sonnet-4-6`, `anthropic/claude-opus-4-6`). Although OpenRouter may accept both hyphen and dot variants, treat the hyphen-separated form as the intended, correct codebase-wide convention and do not flag it as an error. Only flag the dot-separated variant (e.g., `anthropic/claude-sonnet-4.6`) as incorrect when reviewing/validating model ID strings for OpenRouter-routed Anthropic models.

Applied to files:

  • autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
📚 Learning: 2026-04-03T13:50:10.521Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12206
File: autogpt_platform/backend/backend/api/external/v2/integrations/helpers.py:25-46
Timestamp: 2026-04-03T13:50:10.521Z
Learning: In `autogpt_platform/backend/backend/api/external/v2/integrations/helpers.py`, `CredentialInfo.from_internal` is intentionally a read-only external API view that exposes only: id, type, provider, title, scopes, expires_at. It omits internal metadata and secret fields by design. Do not flag omitted fields in CredentialInfo as missing information — the limited field set is the correct external API contract.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-01-19T07:20:23.494Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11795
File: autogpt_platform/backend/backend/api/features/chat/tools/utils.py:92-111
Timestamp: 2026-01-19T07:20:23.494Z
Learning: In autogpt_platform/backend/backend/api/features/chat/tools/utils.py, the _serialize_missing_credential function uses next(iter(field_info.provider)) for provider selection. The PR author confirmed this non-deterministic provider selection is acceptable because the function returns both "type" (single, for backward compatibility) and "types" (full array), which achieves the primary goal of deterministic credential type presentation.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-07T07:43:15.754Z
Learnt from: kcze
Repo: Significant-Gravitas/AutoGPT PR: 12328
File: autogpt_platform/frontend/src/app/api/openapi.json:1116-1118
Timestamp: 2026-03-07T07:43:15.754Z
Learning: In Significant-Gravitas/AutoGPT, v2 chat endpoints often declare HTTPBearerJWT at the router level while using Depends(auth.get_user_id) that returns None for unauthenticated users; effective behavior is optional auth. Keep this convention unless doing a repo-wide OpenAPI update; prefer clarifying descriptions over per-operation security changes.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-15T16:52:15.463Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12426
File: autogpt_platform/backend/backend/copilot/sdk/service.py:0-0
Timestamp: 2026-03-15T16:52:15.463Z
Learning: In Significant-Gravitas/AutoGPT (copilot backend), GitHub tokens (GH_TOKEN / GITHUB_TOKEN) for the `gh` CLI are injected lazily per-command in `autogpt_platform/backend/backend/copilot/tools/bash_exec._execute_on_e2b()` by calling `integration_creds.get_integration_env_vars(user_id)`, not on the global SDK subprocess environment in `sdk/service.py`. This scopes credentials to individual E2B sandbox command invocations and prevents token leakage into tool output streams or uploaded transcripts.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-10T11:22:18.867Z
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.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-10T08:39:22.025Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12356
File: autogpt_platform/backend/backend/copilot/constants.py:9-12
Timestamp: 2026-03-10T08:39:22.025Z
Learning: In Significant-Gravitas/AutoGPT PR `#12356`, the `COPILOT_SYNTHETIC_ID_PREFIX = "copilot-"` check in `create_auto_approval_record` (human_review.py) is intentional and safe. The `graph_exec_id` passed to this function comes from server-side `PendingHumanReview` DB records (not from user input); the API only accepts `node_exec_id` from users. Synthetic `copilot-*` IDs are only ever created server-side in `run_block.py`. The prefix skip avoids a DB lookup for a `AgentGraphExecution` record that legitimately does not exist for CoPilot sessions, while `user_id` scoping is enforced at the auth layer and on the resulting auto-approval record.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-04-09T16:20:43.788Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12727
File: autogpt_platform/backend/backend/data/credit.py:0-0
Timestamp: 2026-04-09T16:20:43.788Z
Learning: In Significant-Gravitas/AutoGPT, `get_user_by_id(user_id: str) -> User` in `autogpt_platform/backend/backend/data/user.py` raises `ValueError("User not found with ID: ...")` when the user row does not exist — it never returns `None`. Do not flag call sites that dereference the result without a None-check as potential null-pointer issues.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-02-27T15:59:00.370Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12213
File: autogpt_platform/frontend/src/app/api/openapi.json:9983-9995
Timestamp: 2026-02-27T15:59:00.370Z
Learning: Repo: Significant-Gravitas/AutoGPT PR: 12213 — OpenAPI/codegen
Learning: Ensuring a field is required in generated TS types needs two sides: (1) no default value on the Pydantic field, and (2) the OpenAPI model's "required" array must list it. For MCPToolInfo, making input_schema required in OpenAPI and removing Field(default_factory=dict) in the backend prevents optional typing drift.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-04T23:58:18.476Z
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.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-04-21T04:35:34.710Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12865
File: autogpt_platform/backend/backend/data/credit.py:1584-1584
Timestamp: 2026-04-21T04:35:34.710Z
Learning: In Significant-Gravitas/AutoGPT, `get_user_by_id(user_id: str)` in `autogpt_platform/backend/backend/data/user.py` returns an application-layer Pydantic `User` model (defined in `autogpt_platform/backend/backend/data/model.py`), NOT the raw Prisma `User` object. This Pydantic model uses snake_case field names (e.g., `subscription_tier`, `stripe_customer_id`, `top_up_config`), which are mapped from camelCase Prisma fields (e.g., `subscriptionTier`, `stripeCustomerId`) inside `User.from_db()`. Do not flag `user.subscription_tier` as a wrong field name — it is correct on the app-layer model.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-01T07:59:02.311Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12213
File: autogpt_platform/frontend/src/app/api/openapi.json:10030-10037
Timestamp: 2026-03-01T07:59:02.311Z
Learning: Repo: Significant-Gravitas/AutoGPT PR: 12213 — For MCP manual token storage, backend model autogpt_platform/backend/backend/api/features/mcp/routes.py defines MCPStoreTokenRequest.token as Pydantic SecretStr with a min length constraint, which generates OpenAPI schema metadata (format: "password", writeOnly: true, minLength: 1) in autogpt_platform/frontend/src/app/api/openapi.json. Prefer SecretStr (with length constraints) for sensitive request fields so generated TS clients and docs treat them as secrets.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-04-09T08:47:32.750Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12720
File: autogpt_platform/backend/backend/copilot/graphiti/client.py:20-46
Timestamp: 2026-04-09T08:47:32.750Z
Learning: In Significant-Gravitas/AutoGPT, `user_id` values passed to `derive_group_id` in `autogpt_platform/backend/backend/copilot/graphiti/client.py` are always system-generated UUIDv4s (e.g. `883cc9da-fe37-4863-839b-acba022bf3ef`). The character set `[0-9a-f-]` is fully within `[a-zA-Z0-9_-]`, so the sanitization regex never strips any characters and no collision between two different user IDs is possible. Do not flag `derive_group_id` for collision-resistance issues.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-17T10:57:12.953Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/copilot/workflow_import/converter.py:0-0
Timestamp: 2026-03-17T10:57:12.953Z
Learning: In Significant-Gravitas/AutoGPT PR `#12440`, `autogpt_platform/backend/backend/copilot/workflow_import/converter.py` was fully rewritten (commit 732960e2d) to no longer make direct LLM/OpenAI API calls. The converter now builds a structured text prompt for AutoPilot/CoPilot instead. There is no `response.choices` access or any direct LLM client usage in this file. Do not flag `response.choices` access or LLM client initialization patterns as issues in this file.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-02-27T15:59:00.370Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12213
File: autogpt_platform/frontend/src/app/api/openapi.json:9983-9995
Timestamp: 2026-02-27T15:59:00.370Z
Learning: Repo: Significant-Gravitas/AutoGPT PR: 12213 — Backend/frontend OpenAPI codegen
Learning: For MCP schema models, required OpenAPI fields must have no defaults in Pydantic. Specifically, MCPToolInfo.input_schema must be required (no Field(default_factory=dict)) so openapi.json emits it in "required", ensuring generated TS types treat input_schema as non-optional.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-04-22T05:58:28.595Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12879
File: autogpt_platform/frontend/src/app/api/openapi.json:14576-14577
Timestamp: 2026-04-22T05:58:28.595Z
Learning: Repo: Significant-Gravitas/AutoGPT — autogpt_platform
Process convention: When adding new CoPilot tool response models and updating ToolResponseUnion in backend/api/features/chat/routes.py, regenerate the frontend OpenAPI schema via `poetry run export-api-schema` (do not hand-edit autogpt_platform/frontend/src/app/api/openapi.json).

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-18T14:03:32.534Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12473
File: autogpt_platform/backend/backend/copilot/tools/agent_browser_integration_test.py:86-195
Timestamp: 2026-03-18T14:03:32.534Z
Learning: In Significant-Gravitas/AutoGPT, the integration tests in `autogpt_platform/backend/backend/copilot/tools/agent_browser_integration_test.py` intentionally use real external URLs (example.com, httpbin.org). They are gated with `pytest.mark.skipif(shutil.which("agent-browser") is None, ...)`, so they are automatically skipped in standard CI where agent-browser is not installed. They are designed to be run explicitly inside the Docker environment to verify that system Chromium (AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium) actually launches and can fetch pages end-to-end. Do not flag the use of real network calls in these tests as a flakiness concern.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-24T21:27:22.326Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12536
File: autogpt_platform/frontend/src/app/api/openapi.json:5732-5752
Timestamp: 2026-03-24T21:27:22.326Z
Learning: Repo: Significant-Gravitas/AutoGPT — Preference: Do not add explicit 403/404 entries to FastAPI route decorators for admin endpoints just to influence OpenAPI. Keep openapi.json autogenerated and use route docstrings to document admin-only (403) and not-found (404) behavior; rely on tests for enforcement. File context: autogpt_platform/backend/backend/api/features/admin/store_admin_routes.py. PR `#12536`.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-24T21:25:15.983Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12536
File: autogpt_platform/frontend/src/app/api/openapi.json:5770-5790
Timestamp: 2026-03-24T21:25:15.983Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12536`
File: autogpt_platform/frontend/src/app/api/openapi.json
Learning: The OpenAPI spec file is auto-generated; per established convention, endpoints generally declare only 200/201, 401, and 422 responses. Do not suggest adding explicit 403/404 response entries for single operations unless planning a repo-wide spec update. Prefer clarifying such behaviors in endpoint descriptions/docstrings instead of altering response maps.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-04-08T17:26:41.549Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: classic/CLAUDE.md:0-0
Timestamp: 2026-04-08T17:26:41.549Z
Learning: Applies to classic/**/tests/**/*.py : Tests requiring API keys (OPENAI_API_KEY, ANTHROPIC_API_KEY) will skip if not set

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-04-07T18:08:03.548Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12701
File: .claude/skills/orchestrate/scripts/verify-complete.sh:120-121
Timestamp: 2026-04-07T18:08:03.548Z
Learning: In Significant-Gravitas/AutoGPT, verify-complete.sh (`.claude/skills/orchestrate/scripts/verify-complete.sh`) uses `commits[-1].committedDate` (not `updatedAt`) to identify stale CHANGES_REQUESTED reviews. This is intentional: `updatedAt` changes on any PR activity (bot comments, label changes, description edits), which would falsely classify a reviewer's CHANGES_REQUESTED as stale — a silent false negative. The `committedDate` edge case (commit created locally before a review but pushed after) only causes a false positive (unnecessary re-brief), which is the safer failure mode. Do not suggest switching to `updatedAt` for this comparison.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-26T00:32:06.673Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12566
File: autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts:968-974
Timestamp: 2026-03-26T00:32:06.673Z
Learning: In Significant-Gravitas/AutoGPT, the admin-facing methods in `autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts` (e.g., `addUserCredits`, `getUsersHistory`, `getUserRateLimit`, `resetUserRateLimit`) intentionally follow the legacy `BackendAPI` pattern with manually defined types in `autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts`. Migrating these admin endpoints to the generated OpenAPI hooks (`@/app/api/__generated__/endpoints/`) is a planned separate effort covering all admin endpoints together, not done piecemeal per PR. Do not flag individual admin type additions in `types.ts` as blocking issues.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-10T08:38:36.655Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12356
File: autogpt_platform/backend/backend/copilot/tools/run_block.py:349-370
Timestamp: 2026-03-10T08:38:36.655Z
Learning: In the AutoGPT CoPilot HITL (Human-In-The-Loop) flow (`autogpt_platform/backend/backend/copilot/tools/run_block.py`), the review card presented to users sets `editable: false`, meaning reviewers cannot modify the input payload. Therefore, credentials resolved before `is_block_exec_need_review()` remain valid and do not need to be recomputed after the review step — the original `input_data` is unchanged through the review lifecycle.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-17T06:48:26.471Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12445
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1071-1072
Timestamp: 2026-03-17T06:48:26.471Z
Learning: In Significant-Gravitas/AutoGPT (autogpt_platform), the AI SDK enforces `z.strictObject({type, errorText})` on SSE `StreamError` responses, so additional fields like `retryable: bool` cannot be added to `StreamError` or serialized via `to_sse()`. Instead, retry signaling for transient Anthropic API errors is done via the `COPILOT_RETRYABLE_ERROR_PREFIX` constant prepended to persisted session messages (in `ChatMessage.content`). The frontend detects retryable errors by checking `markerType === "retryable_error"` from `parseSpecialMarkers()` — no SSE schema changes and no string matching on error text. This pattern was established in PR `#12445`, commit 64d82797b.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-04-09T09:07:11.551Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12720
File: autogpt_platform/backend/backend/copilot/tools/graphiti_delete.py:63-69
Timestamp: 2026-04-09T09:07:11.551Z
Learning: In Significant-Gravitas/AutoGPT, gating `graphiti_delete_user_data` (and similar Graphiti memory tools) on the `is_enabled_for_user` / `graphiti-memory` LaunchDarkly flag in the delete path is intentional and acceptable. The scenario where a user has existing Graphiti data but the flag is later disabled (preventing deletion) is not a concern for the team. Do not flag this pattern as an issue.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-03-12T14:42:40.552Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12385
File: autogpt_platform/backend/backend/copilot/rate_limit.py:141-170
Timestamp: 2026-03-12T14:42:40.552Z
Learning: In Significant-Gravitas/AutoGPT, `check_rate_limit` in `autogpt_platform/backend/backend/copilot/rate_limit.py` is intentionally a "pre-turn soft check" (not a hard atomic reservation). Because LLM token counts are unknown before generation completes, a strict check-and-reserve is impractical. The TOCTOU race (two concurrent turns both passing the pre-check and both committing via `record_token_usage`) is an accepted trade-off. If stricter enforcement is ever needed, the approach is a Lua script doing GET+INCRBY atomically in Redis.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-04-03T13:50:29.037Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12206
File: autogpt_platform/backend/backend/api/external/v2/rate_limit.py:24-56
Timestamp: 2026-04-03T13:50:29.037Z
Learning: In `autogpt_platform/backend/backend/api/external/v2/rate_limit.py`, the `RateLimiter` class uses in-process (per-worker) memory for sliding-window rate limiting. This is intentionally documented as a known limitation via WARNING comments in the module and class docstrings. A full Redis-backed migration (using ZADD/ZREMRANGEBYSCORE/ZCARD with TTL/Lua for atomic multi-replica enforcement) is deferred to a later PR. Do not re-flag the in-memory implementation as a blocking bug — the limitation is documented and accepted for the initial v2 external API release.

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py
📚 Learning: 2026-04-08T17:28:23.439Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/AGENTS.md:0-0
Timestamp: 2026-04-08T17:28:23.439Z
Learning: Applies to autogpt_platform/backend/**/*_test.py : When creating snapshots in tests, use `poetry run pytest path/to/test.py --snapshot-update`; always review snapshot changes with `git diff` before committing

Applied to files:

  • autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py
🔇 Additional comments (3)
autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py (1)

49-109: LGTM on the provider contract and ordering fix.

is_available() → False cleanly opts Ayrshare out of the startup sweep (per the prior review thread), and splitting legacy-clearing into post_provision is the right ordering — if add_managed_credential fails, _provision_under_lock never calls post_provision, so the legacy key survives for a retry. The regression test in ayrshare_test.py::TestMigrationOrderingSafety pins this behavior.

One small observation (non-blocking): post_provision always enters store.edit_user_integrations(...) even on fresh users where the legacy field is already None, so the write-lock path is taken on every provision just to no-op. If edit_user_integrations ends up persisting on exit unconditionally, consider a read-only pre-check (e.g. store._get_user_integrations(user_id)) and only open the edit CM when the legacy field is actually populated. Minor, fine to defer.

autogpt_platform/backend/backend/blocks/ayrshare/post_to_pinterest.py (1)

92-205: LGTM — consistent with the shared Ayrshare credential pattern.

Signature and profile_key=get_profile_key(credentials) call match the established pattern from the other post_to_* blocks (e.g. post_to_instagram.py). Error handling continues to rely on the block executor converting exceptions into the error output, matching the repo convention for block run() methods.

autogpt_platform/backend/backend/integrations/managed_providers/ayrshare_test.py (1)

1-257: LGTM — solid coverage including the migration-ordering regression.

Coverage lines up well with the prior review asks: is_available opt-out, _settings_available variants, legacy-reuse-without-eager-clear, fresh profile creation, post_provision clear + idempotent, managed APIKeyCredentials shape, and critically the retry-safety regression (TestMigrationOrderingSafety) that pins the ordering fix against future reorderings.

Minor nit (non-blocking) on test_post_provision_skips_when_legacy_already_clear (lines 182-184): the comment says "no write attempted", but post_provision unconditionally enters edit_user_integrations, so the CM is still invoked — the test only asserts the final field state. If you actually want to pin "no mutation path taken", consider asserting on the mock (e.g. that no write occurs) or soften the comment to match what's verified.

Comment thread autogpt_platform/backend/backend/integrations/managed_providers/ayrshare.py Outdated
majdyz added 12 commits April 24, 2026 01:23
…r-scoped list

Providers with ``auto_provision=False`` (e.g. Ayrshare) opt out of the
startup sweep because provisioning burns per-user quota.  Consequently,
``GET /{provider}/credentials`` returned an empty list for a brand-new
user — the block UI fell back to "Add API key" instead of showing the
managed cred, defeating the "managed" part of the flow.

The provider-scoped lookup is the right on-demand trigger: the user only
hits it after opening a block that needs that provider, so provisioning
is still bounded.  Inline-provision managed creds for opted-out providers
whose org-level config is available, before returning the list.
…corators

The auto_provision=False opt-out meant Ayrshare was never provisioned from
the global credential-list sweep (which is what the builder uses to
populate block credential dropdowns), so dropping a Post-to-X block
showed an empty dropdown + "Add API key" instead of the managed cred.
The on-demand provider-scoped trigger only fires for the settings page,
not the builder.

Simplest fix: drop the opt-out and let Ayrshare ride the sweep like
AgentMail.  Profile creation is at Ayrshare plan quota, not per-post, so
the cost is bounded at the plan level.  Users who never touch a social
block will have an unused profile slot, which we can accept.

Also restores the @cost(*AYRSHARE_POST_COSTS) decorator + _cost imports
on all 13 Ayrshare blocks — those were stripped during the rebase onto
dev (which had merged #12893 adding the billing hooks).
…d sees it

The `/credentials` endpoint kicked the sweep off as `asyncio.create_task`,
so the endpoint returned BEFORE provisioning finished.  Fine under the
old design (AgentMail was the only provisioning path and it was idempotent
across page loads), but with Ayrshare now riding the same sweep the
first-time user sees an empty dropdown + "Add API key" until they refresh.

Block on `ensure_managed_credentials`.  It fans providers out via
`asyncio.gather`, so the latency is max(provider_times), not sum.  Once
a user is in `_provisioned_users` (in-memory per process) subsequent
calls short-circuit, so this is a one-shot cost per user per pod.
Dev surfaced a Pydantic ValidationError during the sweep: Ayrshare's
GET /profiles returns entries without ``profileKey`` for incomplete /
freshly-created profiles (Sentry predicted this earlier and I got the
fix wrong — was a required str).

Make ``profileKey`` optional and skip matches that lack it in the
recovery lookup so we fall through to ``create_profile`` instead of
returning ``None`` as a key.  Added a regression test for the missing-
key case.
…builder

The block credential picker let the user delete a managed credential
(e.g. "Ayrshare (managed by AutoGPT)"), which would orphan them from the
auto-provisioned cred until the sweep re-ran.  The /profile/integrations
page already gates on ``!cred.is_managed``; match that in the builder's
CredentialsInput/FlatView by dropping ``onDelete`` for managed entries
so the row's context menu never offers the action.
…rror handling

Three items from the latest Sentry review:

1. PostToYouTubeBlock / PostToSnapchatBlock lost their ``is_video=True``
   override during the rebase onto dev.  Without it both blocks
   inherited ``is_video=False``, so the @cost(*AYRSHARE_POST_COSTS)
   filter matched the image tier (2 credits) instead of the video tier
   (5 credits) — under-charging on every run.  Restore the explicit
   True override on both blocks.

2. AyrshareClient was constructing ``Requests`` with the default
   ``raise_for_status=True``, which would raise a generic HTTPError
   before any of our methods could read the JSON body and raise a
   contextual AyrshareAPIException.  Pass ``raise_for_status=False``
   so the custom error-surfacing path runs as intended.

3. ``list_profiles`` ``payload.get("status")`` check — Sentry self-
   resolved this in the previous commit; thread-resolve only.
… fallback

Sentry flagged that the await on ensure_managed_credentials has no
timeout, so if Ayrshare (or any managed provider's upstream) hangs, the
whole /credentials endpoint would block up to the default 5-minute
aiohttp timeout on a first-time user.

Wrap the sweep in asyncio.wait_for with a 10s budget.  On timeout we log
a warning and kick off a background task so provisioning still
completes — the user simply won't see the managed cred until their next
refresh, which is the same guarantee as the old fire-and-forget path
but only kicks in as a fallback when upstream is actually slow.
…share client

Sentry flagged that all four error-handling sites in AyrshareClient
call ``.get("message")`` directly on ``response.json()``, which raises
AttributeError when the body decodes to a list or a bare string.  The
outer handler still surfaces an error, but the user sees a generic
"Provisioning failed" instead of Ayrshare's actual detail.

Extract ``_extract_error_message`` which isinstance-checks the decoded
body and falls back to ``response.text()`` for non-dict shapes, then
use it at all four sites.  Removes the repeated try/except boilerplate
as a side benefit.
Every login auto-provisioning Ayrshare burned a subscription-quota slot
for every user, even those who never used a social-media block.  Switch
to opt-out of the startup sweep and provision only when the user
explicitly clicks the "Connect Social Media Accounts" button — the SSO
URL endpoint already calls ``ensure_managed_credential`` under the
hood, so the provisioning moment moves from "first /credentials call"
to "user signals intent to connect".

To surface the newly-provisioned managed cred without a page refresh,
expose ``CredentialsActionsContext`` with a ``reload`` method and have
AyrshareConnectButton call it after the SSO URL fetch succeeds.

The dropdown stays empty until the user clicks Connect — which is now
the explicit trigger for both profile creation and the social-linking
SSO popup, matching the user's mental model.
…eload

Adds coverage for the two frontend changes in this PR:

- CredentialsFlatView: asserts that rendering a credential with
  ``is_managed: true`` does NOT offer a delete action (the row's
  overflow menu trigger is suppressed via the ``onDelete`` gate).
- AyrshareConnectButton: asserts that a successful SSO URL fetch
  triggers ``credentialsActions.reload()`` on the actions context
  and that a failed fetch does NOT.

Brings codecov/patch on the frontend diff above the 80% threshold.
…ete+create

Ayrshare's GET /profiles never returns ``profileKey`` for any profile —
we verified 0/30 profiles in the dev subscription have one.  That means
there's no supported way to retrieve an existing profile's key via any
Ayrshare endpoint.  When our DB drops the managed credential while the
upstream profile persists, the old idempotency recovery path would
silently skip the match (no key to return) and fall through to
``create_profile``, which then 400s with code 146 "Profile title already
exists" — a 502 for the user with no recovery.

Switch the recovery strategy: when ``list_profiles`` reveals a profile
titled the same as ours, treat it as an orphan and ``delete_profile``
before ``create_profile``.  Destructive — any socials linked to the
orphan are lost — but the orphan is already unusable without the key
(we can't post through it, can't JWT through it), so the alternatives
are either a permanently-stuck user or a shadow profile forever eating
a quota slot.

Adds ``AyrshareClient.delete_profile`` to back the recovery.
Ayrshare is provisioned exclusively via the server-side SSO flow (the
"Connect Social Media Accounts" button on the block).  Showing "Add API
key" / "Use a new API key" next to the credential dropdown mis-suggests
that a user-pasted key is a valid setup path — Toran and Zamil both
tripped over this in dev testing.

Suppress the manual credential-add button in CredentialsFlatView when
the provider is a managed-only one (currently just "ayrshare").  When
the user has no managed credential yet, render a small hint pointing at
the Connect button instead of leaving an empty space.
@majdyz
majdyz force-pushed the fix/copilot-sandbox-gh-bootstrap-and-ayrshare-prompt branch from d94aaa5 to cfeb2db Compare April 23, 2026 18:24
@github-actions github-actions Bot removed the conflicts Automatically applied to PRs with merge conflicts label Apr 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Conflicts have been resolved! 🎉 A maintainer will review the pull request shortly.

Comment thread autogpt_platform/backend/backend/integrations/ayrshare.py Outdated
majdyz added 2 commits April 24, 2026 01:51
…orphan delete

Sentry flagged that Ayrshare's DELETE /profiles requires a Profile-Key
header — which we don't have for orphaned profiles.  So the
"delete + recreate" orphan recovery from the previous commit never
actually worked; the delete would 400 and we'd still hit the duplicate-
title error on create.

Drop the whole recovery codepath.  Append a ``secrets.token_hex(3)``
suffix to every profile title we create — the title is cosmetic in
Ayrshare's dashboard, but unique per provision attempt means we never
collide with an orphaned upstream profile from a prior session.
Orphans stick around in Ayrshare until cleaned up manually; that's the
accepted tradeoff for unblocking users without an endpoint to retrieve
their existing profileKey.

Also removes the now-unused ProfileSummary model, list_profiles method,
delete_profile method, and their tests.  Adds a test asserting titles
are unique across provision attempts.
Two connected UX bugs when an Ayrshare block fails:

1. Ayrshare's POST /post returns HTTP 400 with the actionable reason
   nested at ``posts[0].errors[0].message`` ("Twitter is not linked...")
   — our ``_extract_error_message`` only looked at the root-level
   ``message`` field, so users saw "Unknown error" instead.  Now we dig
   into the ``posts[].errors[]`` structure first and fall back to the
   root message / text.

2. BlockError wrapped every exception with "raised by X with message:
   Y. block_id: Z", which turned the already-wrapped Ayrshare error
   into "raised by PostToXBlock with message: Ayrshare API request
   failed (400): Unknown error. block_id: ..." — double-framed and
   illegible.  Drop the framing; keep ``block_name`` / ``block_id`` as
   attributes for structured logging.  Callers yielding ``str(exc)`` now
   surface just the underlying message.

Combined effect: a failed Post To X now yields "Twitter is not linked.
Please confirm the linkage on the Social Accounts page..." instead of
"raised by PostToXBlock with message: Ayrshare API request failed (400):
Unknown error. block_id: ...".
@majdyz majdyz changed the title [TESTING-NEEDED] refactor(platform): migrate Ayrshare to standard managed-credential flow refactor(platform): migrate Ayrshare to standard managed-credential flow Apr 23, 2026
Previous fix only dug into ``posts[].errors[].message`` — but Ayrshare
also returns request-level rejects with the error directly on the post
object:

  {"posts": [{"action": "post", "code": 162,
   "message": "Missing or incorrect post parameter..."}]}

That shape fires when validation rejects the request before trying any
platform (e.g. empty post field on LinkedIn).  Extract both shapes so
the user sees the real reason instead of "Unknown error".
@majdyz
majdyz merged commit 575f75e into dev Apr 24, 2026
45 checks passed
@majdyz
majdyz deleted the fix/copilot-sandbox-gh-bootstrap-and-ayrshare-prompt branch April 24, 2026 02:37
@github-project-automation github-project-automation Bot moved this to Done in Frontend Apr 24, 2026
@github-project-automation github-project-automation Bot moved this from 🆕 Needs initial review to ✅ Done in AutoGPT development kanban Apr 24, 2026
majdyz added a commit that referenced this pull request Apr 24, 2026
Introduced by #12883 — module-level `import secrets` sat after class/function definitions, tripping ruff E402 on our branch post-merge. One-line hoist unblocks lint.
majdyz added a commit that referenced this pull request Apr 24, 2026
…cept AgentInputBlock subclasses (#12880)

### Why / What / How

**Why:** Resolves #12875. CoPilot's agent-builder was hardcoding Google
Drive file IDs into consuming blocks' `input_default` instead of wiring
an `AgentGoogleDriveFileInputBlock`. A beta user hit this across **13
saved versions** of one agent. Root causes:

1. `validate_io_blocks` only accepted the literal base `AgentInputBlock`
/ `AgentOutputBlock` IDs, so even when CoPilot used a specialized
subclass like `AgentGoogleDriveFileInputBlock` as the only input, the
validator forced it to keep a throwaway base alongside — entrenching the
anti-pattern.
2. Running a Drive consumer directly via CoPilot's `run_block` silently
failed because the auto-credentials flow (picker attaches
`_credentials_id`) existed only in the graph executor, never in
CoPilot's direct-execution path.
3. Drive picker guidance lived in `agent_generation_guide.md` instead of
on the blocks themselves, so it duplicated and drifted from the code.
4. Observed in a live session: when asked to read a private sheet,
CoPilot refused with "share publicly or use the builder" instead of
calling `run_block` and letting the picker render — the prompt rule was
buried and the fallback path (omitted required picker field) returned a
generic schema preview.

**What:** Four coordinated platform + CoPilot improvements. No
block-specific validator rules, no Drive-specific code in UI or prompt.

**How:**

#### 1. `validate_io_blocks` subclass support

Accepts any block with `uiType == "Input"` / `"Output"` (populated from
`Block.block_type` at registration). `AgentGoogleDriveFileInputBlock`,
`AgentDropdownInputBlock`, `AgentTableInputBlock`, etc. stand alone.
Base-ID fallback preserved for call sites that pass a minimal blocks
list.

#### 2. Inline picker via `run_block`

- Extracted `_acquire_auto_credentials` from
`backend/executor/manager.py` into shared
`backend/executor/auto_credentials.py` (exports
`acquire_auto_credentials` + `MissingAutoCredentialsError`).
- Wired it into `backend/copilot/tools/helpers.py::execute_block`. When
`_credentials_id` is present, the block executes with creds injected
(chained flows work). When missing/null, `execute_block` returns the
existing `SetupRequirementsResponse` — frontend's `FormRenderer` renders
the picker inline via the existing
`GoogleDrivePickerField`/`GoogleDrivePickerInput`. On pick, the LLM
re-invokes `run_block` with the populated input — same continuation
pattern as OAuth-missing-credentials. No new response types, no new
continuation tool, no new frontend component.
- `run_block` now short-circuits to `SetupRequirementsResponse` when
missing required fields include a picker-backed field, skipping the
schema-preview round trip the LLM would otherwise take.
- `get_inputs_from_schema` spreads the full property schema (`**schema`)
instead of whitelisting — any `format` / `json_schema_extra` / custom
widget config flows through to the generic custom-field dispatch on the
frontend. Future picker formats (date pickers, file pickers, etc.) work
without backend changes.
- Frontend `SetupRequirementsCard/helpers.ts` uses index-signature
passthrough for arbitrary schema keys — no widget-specific code in that
layer.

#### 3. `validate_only` parameter on `run_block`

`run_block(id, {})` is not always a safe probe — for blocks with zero
required inputs, it executes. New `validate_only: true` parameter
returns `BlockDetailsResponse` (schema + missing-input list) without
executing, rendering picker cards, or charging credits. Same response
shape as the existing schema preview — no new branch, just an extra
condition on the existing one. LLM uses this for pre-flight when it's
unsure whether a block has required inputs.

#### 4. Block-local picker guidance

Agent-generation picker guidance relocated from the guide onto the
blocks themselves — surfaced at `find_block` time, exactly when the LLM
decides to wire a picker-backed consumer:

- `GoogleDriveFileField` (shared factory for every Drive field on
Sheets/Docs/etc.) appends a standard hint to the caller's description
covering: feed from the specialized input block, never hardcode (even
one parsed from a URL), picker is the only credential source.
- `AgentGoogleDriveFileInputBlock`'s block description now covers when
it's required, the `allowed_views` mapping, wiring direction, and a
concrete link-shape example.
- `agent_generation_guide.md` loses the dedicated 71-line Drive section.
The IO-blocks section now tells the LLM specialized subclasses satisfy
the requirement and carry their own usage guidance in block/field
descriptions — read them when `find_block` surfaces a match.
- New "Picker-backed inputs via `run_block`" section in the CoPilot
prompt, written generically (picker fields detected via `format` /
`auto_credentials` schema hints, no provider names hardcoded) — covers:
don't ask the user for URLs/IDs, don't refuse private-resource asks,
chained picker objects pass through as-is.
- Sharpened `MissingAutoCredentialsError` message so when a bare ID
reaches execution, the error explicitly tells the LLM the picker renders
inline (not "ask the user for something").

### Changes 🏗️

- `backend/copilot/tools/agent_generator/validator.py` —
`_collect_io_block_ids` + subclass-aware `validate_io_blocks`.
- `backend/executor/auto_credentials.py` (new) — shared
`acquire_auto_credentials` + `MissingAutoCredentialsError`.
- `backend/executor/manager.py` — imports from the shared module, drops
the local copy.
- `backend/copilot/tools/helpers.py` — `execute_block` calls
`acquire_auto_credentials`, merges kwargs, releases locks in `finally`,
returns `SetupRequirementsResponse` on missing creds.
`get_inputs_from_schema` spreads the full property schema.
- `backend/copilot/tools/run_block.py` — picker-field short-circuit +
`validate_only` parameter.
- `backend/copilot/prompting.py` — "Picker-backed inputs via
`run_block`" + "Pre-flight with `validate_only`" sections.
- `backend/blocks/google/_drive.py` — `GoogleDriveFileField` appends the
agent-builder hint to every Drive consumer's description.
- `backend/blocks/io.py` — `AgentGoogleDriveFileInputBlock` description
expanded.
- `backend/copilot/sdk/agent_generation_guide.md` — Drive section
removed, IO-blocks subclass note expanded.
- `frontend/.../SetupRequirementsCard/helpers.ts` — index-signature
passthrough for arbitrary schema keys; schema fields propagate into the
generated RJSF schema.
- Tests: new `TestExecuteBlockAutoCredentials` (4 cases) +
`validate_only` + picker-short-circuit cases in `run_block_test.py`;
`manager_auto_credentials_test.py` moved to new import path; 6 new
frontend cases in `SetupRequirementsCard/__tests__/helpers.test.ts`
covering schema passthrough.
- Also: one-line hoist of `import secrets` in
`backend/integrations/managed_providers/ayrshare.py` — ruff E402
introduced by #12883 was blocking our lint post-merge.

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] Backend unit suites: validator_test (48), helpers_test (40),
run_block_test (19), manager_auto_credentials_test (15) — **all green**
- [x] Frontend `SetupRequirementsCard` helpers — **75/75 pass**
(including 6 new passthrough cases)
- [x] `poetry run format` (ruff + isort + black) clean on touched files
(pre-existing pyright errors in unrelated `graphiti_core` /
`StreamEvent` / etc. files not introduced by this PR)
- [x] Live CoPilot chat on dev-builder confirmed the setup card renders
`custom/google_drive_picker_field` for a Drive consumer block called via
`run_block`
- [x] Live agent-generation confirmed CoPilot creates a subclass-only
agent (`AgentGoogleDriveFileInputBlock` → `GoogleSheetsReadBlock` →
`AgentOutputBlock`) with no throwaway base `AgentInputBlock`

#### For configuration changes:
- [x] N/A — no config changes

---------

Co-authored-by: majdyz <zamil.majdy@agpt.co>
majdyz added a commit that referenced this pull request Apr 24, 2026
…d sweep (#12908)

## Why

Review comments on #12883 (thanks @Pwuts) surfaced a few spots where the
managed-credential plumbing's names and docstrings didn't match what the
code actually does:

- `_read_or_create_profile_key` suggests "read from any source or create
new", but only migrates the legacy
`managed_credentials.ayrshare_profile_key` side-channel — it doesn't
read an existing managed credential. (That check lives in the outer
`_provision_under_lock`.)
- Docstrings refer to "the startup sweep" in several places — there's no
startup hook; the sweep runs on `/credentials` fetches.
- `is_available` / `auto_provision` relationship wasn't explicit;
readers couldn't tell whether `is_available` was a config check or a
liveness check, or which of the two gates the sweep checks first.

## What

Naming + docstring cleanup. **Zero behavior changes.**

- Rename `_read_or_create_profile_key` →
`_migrate_legacy_or_create_profile_key` with docstring explaining why it
doesn't re-check the managed cred.
- Replace "startup sweep" → "credentials sweep" everywhere.
- `ManagedCredentialProvider` class docstring now names the two gates:
1. `auto_provision` — does this provider participate in the sweep at
all?
  2. `is_available` — are the required env vars / secrets set?
- `is_available` docstring now spells out: what it checks (env vars),
what it does NOT check (upstream health), and that it's only consulted
when `auto_provision=True`.
- `ensure_managed_credentials` docstring defines "credentials sweep",
when it fires, how the per-user in-memory cache works.
- Module-level docstring drops the stale "non-blocking background task"
wording (#12883 made the sweep bounded-await).

## How

4 files, all backend:
- `backend/integrations/managed_credentials.py`
- `backend/integrations/managed_providers/ayrshare.py`
- `backend/integrations/managed_providers/ayrshare_test.py`
- `backend/api/features/integrations/router.py`

Tests: 13/13 Ayrshare tests pass against the rename.

## Checklist

- [x] Follows style guide
- [x] Existing tests still pass (no functional change)
- [x] No new tests needed — pure rename + docstring change
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform/backend AutoGPT Platform - Back end platform/blocks platform/frontend AutoGPT Platform - Front end size/xl

Projects

Status: ✅ Done
Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants