Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
5ca49a8
fix(copilot): P0 guardrails — SDK limits, security env vars, transien…
majdyz Apr 1, 2026
2e8e115
fix(copilot): review round 1 — fix transient retry count, strip fallb…
majdyz Apr 1, 2026
4ebe294
fix(copilot): review round 2 — fix transient retry consuming context-…
majdyz Apr 1, 2026
dced534
fix(copilot): review round 3 — fix transient error code check, add SD…
majdyz Apr 1, 2026
98c2765
fix(copilot): snapshot/restore TranscriptBuilder on transient retry
majdyz Apr 1, 2026
d317360
test(copilot): add unit tests for P0 guardrails
majdyz Apr 1, 2026
a369fbe
fix(copilot): replace tautological env-var tests with source assertions
majdyz Apr 1, 2026
3f24a00
fix(copilot): add None guard to fix pyright reportOperatorIssue
majdyz Apr 1, 2026
e753aee
fix(copilot): prevent infinite transient retry loop
majdyz Apr 1, 2026
61513b9
fix(copilot): mock build_sdk_env to return {} instead of None in retr…
majdyz Apr 1, 2026
b220fe4
test(copilot): add build_sdk_env tests for all 3 auth modes
majdyz Apr 1, 2026
32bfe1b
Merge branch 'dev' of github.com:Significant-Gravitas/AutoGPT into fi…
majdyz Apr 1, 2026
b3d9e9e
fix(backend): add 429/5xx patterns to is_transient_api_error and add …
majdyz Apr 2, 2026
22e8c5c
fix(copilot): update response_adapter test for expanded transient pat…
majdyz Apr 2, 2026
3e6faf2
fix(copilot): address remaining should-fix items from reviewer
majdyz Apr 2, 2026
a55653f
fix(backend): tighten fallback model detection and reset flag on retry
majdyz Apr 2, 2026
8de935c
dx(backend/copilot): add round 3 E2E test screenshots for PR #12636
majdyz Apr 2, 2026
e3d589b
fix(backend/copilot): exclude StreamError/StreamStatus from events_yi…
majdyz Apr 2, 2026
c2f421c
dx(backend/copilot): add live execution guardrail verification for PR…
majdyz Apr 2, 2026
e57e482
security: remove test artifacts containing leaked API keys and OAuth …
majdyz Apr 2, 2026
289a19d
Merge branch 'dev' of github.com:Significant-Gravitas/AutoGPT into fi…
majdyz Apr 2, 2026
2bf5a37
fix(backend): add ge/le bounds to claude_agent_max_transient_retries …
majdyz Apr 2, 2026
a68f48e
Merge branch 'dev' of github.com:Significant-Gravitas/AutoGPT into fi…
majdyz Apr 2, 2026
2a969e5
fix(backend/copilot): yield final StreamError after transient retry e…
majdyz Apr 2, 2026
eda02f9
fix(backend/copilot): remove duplicate StreamError in _HandledStreamE…
majdyz Apr 2, 2026
eeb2f08
merge: resolve conflict with dev (use build_sdk_env(sdk_cwd=) from #1…
majdyz Apr 2, 2026
49bef40
Merge branch 'dev' of github.com:Significant-Gravitas/AutoGPT into fi…
majdyz Apr 2, 2026
2411cc3
fix(backend/copilot): update p0 guardrail tests to check env.py after…
majdyz Apr 2, 2026
bddc633
fix(copilot): increase guardrail defaults — max_turns=1000, max_budge…
majdyz Apr 3, 2026
de094ee
Merge branch 'dev' of github.com:Significant-Gravitas/AutoGPT into fi…
majdyz Apr 3, 2026
b101069
Merge remote-tracking branch 'origin/dev' into work/pr-12636
majdyz Apr 7, 2026
1a52b0d
fix(copilot): address review comments — security env vars for all aut…
majdyz Apr 7, 2026
e2add1b
Merge remote-tracking branch 'origin/dev' into fix/copilot-p0-cli-int…
majdyz Apr 7, 2026
644d39d
Merge branch 'fix/copilot-p0-cli-internals' of github.com:Significant…
majdyz Apr 7, 2026
8a4bc0b
fix(copilot): persist retryable marker when transient retries exhausted
majdyz Apr 7, 2026
79b8ad8
fix(copilot): tighten fallback detection pattern and harden header sa…
majdyz Apr 8, 2026
f95772f
fix(copilot): fix StreamError ordering and cap exponential backoff
majdyz Apr 8, 2026
fff9faf
fix(copilot): use transient_api_error code for exhausted transient re…
majdyz Apr 8, 2026
3a6418f
refactor(copilot): extract _compute_transient_backoff to module level
majdyz Apr 8, 2026
eadc946
refactor(copilot): compile header-safe regex at module level and clar…
majdyz Apr 8, 2026
ba591e7
test(copilot): add _safe() edge-case tests and events_yielded exclusi…
majdyz Apr 8, 2026
84327fe
refactor(copilot): extract _next_transient_backoff and _do_transient_…
majdyz Apr 8, 2026
5db531a
Merge remote-tracking branch 'origin/dev' into fix/copilot-p0-cli-int…
majdyz Apr 8, 2026
a5560bc
fix(copilot): address review blockers and should-fix items
majdyz Apr 8, 2026
266e25c
fix(copilot): preserve fallback_model_activated across transient retries
majdyz Apr 8, 2026
9d6623c
Merge branch 'dev' of github.com:Significant-Gravitas/AutoGPT into fi…
majdyz Apr 8, 2026
47a869a
fix(copilot): use production _EPHEMERAL_EVENT_TYPES in TestEventsYiel…
majdyz Apr 8, 2026
afe7380
test(copilot): add integration tests for transient retry paths and fi…
majdyz Apr 9, 2026
84b996c
fix(copilot): reconstruct JSONL transcript from session messages when…
majdyz Apr 9, 2026
025ff4d
fix(backend/copilot): clear OAuth tokens in SDK env for non-subscript…
majdyz Apr 9, 2026
f0f56d3
fix(backend/copilot): remove unverified CLAUDE_CODE_SKIP_PROMPT_HISTO…
majdyz Apr 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions autogpt_platform/backend/backend/copilot/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,32 @@ class ChatConfig(BaseSettings):
description="Use --resume for multi-turn conversations instead of "
"history compression. Falls back to compression when unavailable.",
)
claude_agent_fallback_model: str = Field(
default="claude-sonnet-4-20250514",
description="Fallback model when the primary model is unavailable (e.g. 529 "
"overloaded). The SDK automatically retries with this cheaper model.",
)
claude_agent_max_turns: int = Field(
default=1000,
Comment thread
majdyz marked this conversation as resolved.
Comment thread
majdyz marked this conversation as resolved.
ge=1,
le=10000,
description="Maximum number of agentic turns (tool-use loops) per query. "
"Prevents runaway tool loops from burning budget.",
)
claude_agent_max_budget_usd: float = Field(
Comment thread
majdyz marked this conversation as resolved.
default=100.0,
ge=0.01,
Comment thread
majdyz marked this conversation as resolved.
le=1000.0,
description="Maximum spend in USD per SDK query. The CLI aborts the "
Comment thread
majdyz marked this conversation as resolved.
Comment thread
majdyz marked this conversation as resolved.
Comment thread
majdyz marked this conversation as resolved.
"request if this budget is exceeded.",
Comment thread
majdyz marked this conversation as resolved.
)
Comment thread
majdyz marked this conversation as resolved.
claude_agent_max_transient_retries: int = Field(
Comment thread
majdyz marked this conversation as resolved.
default=3,
ge=0,
le=10,
description="Maximum number of retries for transient API errors "
"(429, 5xx, ECONNRESET) before surfacing the error to the user.",
)
use_openrouter: bool = Field(
default=True,
description="Enable routing API calls through the OpenRouter proxy. "
Expand Down
21 changes: 20 additions & 1 deletion autogpt_platform/backend/backend/copilot/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,31 @@ def parse_node_id_from_exec_id(node_exec_id: str) -> str:
# Transient Anthropic API error detection
# ---------------------------------------------------------------------------
# Patterns in error text that indicate a transient Anthropic API error
# (ECONNRESET / dropped TCP connection) which is retryable.
# which is retryable. Covers:
# - Connection-level: ECONNRESET, dropped TCP connections
# - HTTP 429: rate-limit / too-many-requests
Comment thread
majdyz marked this conversation as resolved.
# - HTTP 5xx: server errors
Comment thread
majdyz marked this conversation as resolved.
#
# Prefer specific status-code patterns over natural-language phrases
# (e.g. "overloaded", "bad gateway") — those phrases can appear in
Comment thread
majdyz marked this conversation as resolved.
# application-level SDK messages and would trigger spurious retries.
Comment thread
majdyz marked this conversation as resolved.
_TRANSIENT_ERROR_PATTERNS = (
Comment thread
majdyz marked this conversation as resolved.
# Connection-level
Comment thread
majdyz marked this conversation as resolved.
"socket connection was closed unexpectedly",
Comment thread
majdyz marked this conversation as resolved.
"ECONNRESET",
Comment thread
majdyz marked this conversation as resolved.
"connection was forcibly closed",
"network socket disconnected",
Comment thread
majdyz marked this conversation as resolved.
# 429 rate-limit patterns
"rate limit",
"rate_limit",
"too many requests",
"status code 429",
# 5xx server error patterns (status-code-specific to avoid false positives)
"status code 529",
"status code 500",
Comment thread
majdyz marked this conversation as resolved.
"status code 502",
"status code 503",
"status code 504",
)

FRIENDLY_TRANSIENT_MSG = "Anthropic connection interrupted — please retry"
Expand Down
66 changes: 36 additions & 30 deletions autogpt_platform/backend/backend/copilot/sdk/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ def build_sdk_env(

Three modes (checked in order):
1. **Subscription** — clears all keys; CLI uses ``claude login`` auth.
2. **Direct Anthropic** — returns ``{}``; subprocess inherits
``ANTHROPIC_API_KEY`` from the parent environment.
2. **Direct Anthropic** — subprocess inherits ``ANTHROPIC_API_KEY``
from the parent environment (no overrides needed).
3. **OpenRouter** (default) — overrides base URL and auth token to
route through the proxy, with Langfuse trace headers.

When *sdk_cwd* is provided, ``CLAUDE_CODE_TMPDIR`` is set so that
the CLI writes temp/sub-agent output inside the per-session workspace
directory rather than an inaccessible system temp path.
All modes receive workspace isolation (``CLAUDE_CODE_TMPDIR``) and
security hardening env vars to prevent .claude.md loading, prompt
history persistence, auto-memory writes, and non-essential traffic.
"""
# --- Mode 1: Claude Code subscription auth ---
if config.use_claude_code_subscription:
Expand All @@ -43,40 +43,46 @@ def build_sdk_env(
"ANTHROPIC_AUTH_TOKEN": "",
"ANTHROPIC_BASE_URL": "",
}
if sdk_cwd:
env["CLAUDE_CODE_TMPDIR"] = sdk_cwd
return env

# --- Mode 2: Direct Anthropic (no proxy hop) ---
if not config.openrouter_active:
elif not config.openrouter_active:
env = {}
if sdk_cwd:
env["CLAUDE_CODE_TMPDIR"] = sdk_cwd
return env

# --- Mode 3: OpenRouter proxy ---
base = (config.base_url or "").rstrip("/")
if base.endswith("/v1"):
base = base[:-3]
env = {
"ANTHROPIC_BASE_URL": base,
"ANTHROPIC_AUTH_TOKEN": config.api_key or "",
"ANTHROPIC_API_KEY": "", # force CLI to use AUTH_TOKEN
}
else:
base = (config.base_url or "").rstrip("/")
if base.endswith("/v1"):
base = base[:-3]
Comment thread
majdyz marked this conversation as resolved.
env = {
"ANTHROPIC_BASE_URL": base,
"ANTHROPIC_AUTH_TOKEN": config.api_key or "",
"ANTHROPIC_API_KEY": "", # force CLI to use AUTH_TOKEN
}
Comment thread
majdyz marked this conversation as resolved.

Comment thread
majdyz marked this conversation as resolved.
Comment thread
majdyz marked this conversation as resolved.
# Inject broadcast headers so OpenRouter forwards traces to Langfuse.
def _safe(v: str) -> str:
return v.replace("\r", "").replace("\n", "").strip()[:128]
# Inject broadcast headers so OpenRouter forwards traces to Langfuse.
def _safe(v: str) -> str:
return v.replace("\r", "").replace("\n", "").strip()[:128]
Comment thread
majdyz marked this conversation as resolved.
Outdated

parts = []
if session_id:
parts.append(f"x-session-id: {_safe(session_id)}")
if user_id:
parts.append(f"x-user-id: {_safe(user_id)}")
if parts:
env["ANTHROPIC_CUSTOM_HEADERS"] = "\n".join(parts)
parts = []
if session_id:
Comment thread
majdyz marked this conversation as resolved.
Comment thread
majdyz marked this conversation as resolved.
Comment thread
majdyz marked this conversation as resolved.
parts.append(f"x-session-id: {_safe(session_id)}")
Comment thread
majdyz marked this conversation as resolved.
if user_id:
parts.append(f"x-user-id: {_safe(user_id)}")
if parts:
env["ANTHROPIC_CUSTOM_HEADERS"] = "\n".join(parts)

# --- Common: workspace isolation + security hardening (all modes) ---
# Route subagent temp files into the per-session workspace so output
# files are accessible (fixes /tmp/claude-0/ permission errors in E2B).
if sdk_cwd:
Comment thread
majdyz marked this conversation as resolved.
env["CLAUDE_CODE_TMPDIR"] = sdk_cwd

Comment thread
majdyz marked this conversation as resolved.
# Harden multi-tenant deployment: prevent loading untrusted workspace
# .claude.md files, persisting prompt history, writing auto-memory,
# and sending non-essential telemetry traffic.
env["CLAUDE_CODE_DISABLE_CLAUDE_MDS"] = "1"
Comment thread
majdyz marked this conversation as resolved.
Comment thread
majdyz marked this conversation as resolved.
env["CLAUDE_CODE_SKIP_PROMPT_HISTORY"] = "1"
Comment thread
majdyz marked this conversation as resolved.
Outdated
env["CLAUDE_CODE_DISABLE_AUTO_MEMORY"] = "1"
env["CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"] = "1"

return env
34 changes: 18 additions & 16 deletions autogpt_platform/backend/backend/copilot/sdk/env_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,9 @@ def test_returns_blanked_keys(self, mock_validate):

result = build_sdk_env()

assert result == {
"ANTHROPIC_API_KEY": "",
"ANTHROPIC_AUTH_TOKEN": "",
"ANTHROPIC_BASE_URL": "",
}
assert result["ANTHROPIC_API_KEY"] == ""
assert result["ANTHROPIC_AUTH_TOKEN"] == ""
assert result["ANTHROPIC_BASE_URL"] == ""
mock_validate.assert_called_once()

@patch(
Expand All @@ -68,18 +66,20 @@ def test_propagates_validation_error(self, mock_validate):


class TestBuildSdkEnvDirectAnthropic:
"""When OpenRouter is inactive, return empty dict (inherit parent env)."""
"""When OpenRouter is inactive, no ANTHROPIC_* overrides (inherit parent env)."""

def test_returns_empty_dict_when_openrouter_inactive(self):
def test_no_anthropic_key_overrides_when_openrouter_inactive(self):
cfg = _make_config(use_openrouter=False)
with patch("backend.copilot.sdk.env.config", cfg):
from backend.copilot.sdk.env import build_sdk_env

result = build_sdk_env()

assert result == {}
assert "ANTHROPIC_API_KEY" not in result
assert "ANTHROPIC_AUTH_TOKEN" not in result
assert "ANTHROPIC_BASE_URL" not in result

def test_returns_empty_dict_when_openrouter_flag_true_but_no_key(self):
def test_no_anthropic_key_overrides_when_openrouter_flag_true_but_no_key(self):
"""OpenRouter flag is True but no api_key => openrouter_active is False."""
cfg = _make_config(use_openrouter=True, base_url="https://openrouter.ai/api/v1")
# Force api_key to None after construction (field_validator may pick up env vars)
Expand All @@ -90,7 +90,9 @@ def test_returns_empty_dict_when_openrouter_flag_true_but_no_key(self):

result = build_sdk_env()

assert result == {}
assert "ANTHROPIC_API_KEY" not in result
assert "ANTHROPIC_AUTH_TOKEN" not in result
assert "ANTHROPIC_BASE_URL" not in result


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -234,12 +236,12 @@ def test_subscription_overrides_openrouter(self, mock_validate):

result = build_sdk_env()

# Should get subscription result, not OpenRouter
assert result == {
"ANTHROPIC_API_KEY": "",
"ANTHROPIC_AUTH_TOKEN": "",
"ANTHROPIC_BASE_URL": "",
}
# Should get subscription result (blanked keys), not OpenRouter proxy
assert result["ANTHROPIC_API_KEY"] == ""
assert result["ANTHROPIC_AUTH_TOKEN"] == ""
assert result["ANTHROPIC_BASE_URL"] == ""
# OpenRouter-specific key must NOT be present
assert "ANTHROPIC_CUSTOM_HEADERS" not in result


# ---------------------------------------------------------------------------
Expand Down
Loading
Loading