Skip to content

fix(copilot): multi-layer defence against Write tool truncation errors - #12749

Closed
majdyz wants to merge 1 commit into
devfrom
fix/write-tool-truncation
Closed

fix(copilot): multi-layer defence against Write tool truncation errors#12749
majdyz wants to merge 1 commit into
devfrom
fix/write-tool-truncation

Conversation

@majdyz

@majdyz majdyz commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Layer 1: Disable the CLI built-in Write tool (added to SDK_DISALLOWED_TOOLS) so all file writes route through our MCP write_file / write_workspace_file tools where we control schema validation and error messages
  • Layer 2: Detect partial truncation in tool_adapter.py — when content/new_string is present but file_path is missing, return actionable chunking guidance instead of letting the call fail with an opaque Ajv error
  • Layer 3: Warn on large inline content (>50K chars) in E2B write_file — if the write succeeds but was dangerously close to the truncation threshold, suggest chunking for next time
  • Layer 4: Improve the workspace_files.py truncation error message with step-by-step instructions (cat >, cat >>, source_path)
  • Layer 5: Strengthen the system prompt guidance about large file writes to be more emphatic about the production failure mode

Root cause

When the LLM generates a very large content argument for a Write tool call, the API output-token limit truncates the response mid-JSON. The CLI's built-in Ajv JSON Schema validator then rejects the truncated arguments with 'file_path' is a required property — an opaque, unhelpful error. The user's work is lost and the LLM retries with the same approach, failing in a loop.

Affected sessions

  • 1a0ef47f-9711-41d2-91b8-df9dff9ecfc6
  • 2bb9fb0d-05bd-4194-8f3f-88fbe3d8b965
  • 8226d82e-bf9c-48e3-826f-80048d0fb7b4

Test plan

  • Unit tests for partial-truncation detection in tool_adapter_test.py (4 cases: write_file truncated, edit_file truncated, write_file with file_path passes through, non-write tool not affected)
  • Unit tests for improved workspace_files truncation error message (empty args, missing filename with content)
  • Verify ruff check and pyright pass on all modified files
  • Manual test: confirm large inline write_file calls return actionable guidance instead of opaque errors

When the LLM generates a very large `content` argument for a write tool
call, the API output-token limit truncates the response mid-JSON.  The
CLI's built-in Ajv validator then rejects it with the opaque error
"'file_path' is a required property", losing the user's work and causing
retry loops.

Layer 1 — Disable the CLI built-in Write tool so all writes go through
our MCP tools where we control validation and error messages.

Layer 2 — Detect partial truncation in tool_adapter.py: when content/
new_string is present but file_path is missing, return actionable
chunking guidance instead of letting the call fail opaquely.

Layer 3 — Warn on large inline content in e2b write_file: if the write
succeeds but content was very large, suggest chunking for next time.

Layer 4 — Improve the workspace_files.py truncation error message with
step-by-step instructions (cat >, cat >>, source_path).

Layer 5 — Strengthen the system prompt guidance about large file writes.

Reported in sessions:
- 1a0ef47f-9711-41d2-91b8-df9dff9ecfc6
- 2bb9fb0d-05bd-4194-8f3f-88fbe3d8b965
- 8226d82e-bf9c-48e3-826f-80048d0fb7b4
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Apr 11, 2026
@majdyz
majdyz requested a review from a team as a code owner April 11, 2026 16:58
@majdyz
majdyz requested review from 0ubbe and kcze and removed request for a team April 11, 2026 16:58
@github-actions github-actions Bot added platform/backend AutoGPT Platform - Back end size/l labels Apr 11, 2026
@coderabbitai

coderabbitai Bot commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request enhances handling of truncated file write operations when content exceeds token limits. It introduces partial truncation detection in the MCP tool wrapper, adds warnings for large files, updates error messages with actionable remediation guidance, and includes comprehensive test coverage.

Changes

Cohort / File(s) Summary
Truncation Detection & Warnings
backend/copilot/sdk/tool_adapter.py, backend/copilot/sdk/e2b_file_tools.py
Added logic to detect partial truncation in write tools (when content is present but file_path is missing), returning error with chunked-write guidance. Added 50K character threshold warning when content exceeds limit, logging and appending advisory to response.
Error Messages & Prompting
backend/copilot/tools/workspace_files.py, backend/copilot/prompting.py
Updated WriteWorkspaceFileTool error message with step-by-step shell redirection instructions for chunked writes. Revised "Writing large files — CRITICAL" guidance text to clarify truncation failure modes as mid-JSON errors causing unrecoverable loops rather than silent empty inputs.
Test Coverage
backend/copilot/sdk/tool_adapter_test.py, backend/copilot/tools/workspace_files_test.py
Added TestPartialTruncationDetection suite validating wrapper behavior when file_path is missing for write tools. Added TestWriteTruncationMessage suite asserting error responses correctly differentiate truncation vs. missing-argument scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant MCP Wrapper
    participant File Tool
    participant Logger
    
    Client->>MCP Wrapper: call write_file(content, [missing file_path])
    
    MCP Wrapper->>MCP Wrapper: detect partial truncation<br/>(content exists, file_path missing)
    
    alt Partial Truncation Detected
        MCP Wrapper->>Logger: warn truncation detected
        MCP Wrapper->>Client: return error + chunked-write guidance
    else Normal Flow
        MCP Wrapper->>File Tool: forward call
        File Tool->>File Tool: check content length
        alt Content > 50K chars
            File Tool->>Logger: log large file warning
            File Tool->>Client: return success + warning advisory
        else Normal Size
            File Tool->>Client: return success
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

size/l, platform/backend

Suggested reviewers

  • Swiftyos
  • Pwuts
  • 0ubbe
  • Bentlybro
  • kcze

Poem

🐰 A file too large? The wrapper now sees,
Truncation's cruel mid-JSON disease,
With cat >> whispers and chunked-write care,
No silent failures in the air!
Guidance and warnings, the path made clear.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(copilot): multi-layer defence against Write tool truncation errors' clearly and specifically describes the main change — implementing multiple defensive layers against write tool truncation errors, which is the core focus of all file changes.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing five layers of defense against write tool truncation, root causes, affected sessions, and test plans that align with the actual code changes.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/write-tool-truncation

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

❤️ Share

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

@github-actions

Copy link
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, 1 low risk (out of 1 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: 3

Caution

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

⚠️ Outside diff range comments (1)
autogpt_platform/backend/backend/copilot/tools/workspace_files.py (1)

791-820: ⚠️ Potential issue | 🔴 Critical

has_any_content never sees the bound content args.

content, content_base64, and source_path are explicit parameters, so they are not present in kwargs. On Line 797 this makes has_any_content false even for calls like filename=None, content="some content", so the method returns the truncation message instead of the simple “Please provide a filename” error. The new test at Line 655 will fail because of this.

Proposed fix
         if not filename:
@@
-            has_any_content = any(
-                kwargs.get(k) for k in ("content", "content_base64", "source_path")
-            )
+            has_any_content = any(
+                value not in (None, "")
+                for value in (content, content_base64, source_path)
+            )
             if not has_any_content:
                 return ErrorResponse(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/copilot/tools/workspace_files.py` around
lines 791 - 820, The has_any_content check incorrectly inspects kwargs for
explicit parameters (content, content_base64, source_path) which will never be
in kwargs; update the logic in the containing function (the block using
has_any_content) to consider both bound parameters and kwargs — e.g., check the
explicit parameters content, content_base64, source_path as well as
kwargs.get(...) — so calls like filename=None, content="..." are detected
correctly and the truncation message is only returned when none of those sources
contain content; keep the existing ErrorResponse behavior and session_id usage.
🤖 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/copilot/sdk/tool_adapter_test.py`:
- Around line 939-1029: Add a new test in TestPartialTruncationDetection that
mirrors test_write_file_partial_truncation_detected but uses the
"write_workspace_file" tool name so the truncation guard for workspace writes is
exercised; use _make_truncating_wrapper(fake_tool_fn, "write_workspace_file",
input_schema={"required": ["filename", "content"]}) and call wrapper({"content":
"some data"}) asserting result["isError"] is True and the returned text mentions
"truncated" and the missing "filename"/"file_path" as appropriate; also add a
positive case like test_write_file_with_file_path_passes_through but for
write_workspace_file to ensure calls with "filename" pass through and the fake
tool is invoked.

In `@autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py`:
- Around line 530-550: The guard treats any call to write_workspace_file with
inline content as truncated because write_workspace_file doesn't use file_path;
update the conditional in the truncation-detection block to either exclude
"write_workspace_file" from the tool_name tuple or to check allowed payload keys
for that tool (e.g., treat write_workspace_file as valid if args contains
"content" or "content_base64" or "source_path"); ensure tools that truly need
file_path (write_file, edit_file) still trigger the _mcp_error when "file_path"
is missing, and keep the existing logger.warning and _mcp_error behavior for
those cases.

In `@autogpt_platform/backend/backend/copilot/tools/workspace_files_test.py`:
- Around line 638-666: The tests currently use "# type: ignore[arg-type]" for
filename=None; instead update the implementation rather than suppressing types:
change WriteWorkspaceFileTool._execute (and any interface it implements) to
accept filename: str | None (or overloads/typed helper e.g.,
_execute_with_optional_filename) and handle the None case (truncated/malformed
vs missing required field) internally, then remove the type ignores from tests;
ensure any callers or type stubs of WriteWorkspaceFileTool._execute are updated
to the new signature so mypy/pyright no longer requires suppressors.

---

Outside diff comments:
In `@autogpt_platform/backend/backend/copilot/tools/workspace_files.py`:
- Around line 791-820: The has_any_content check incorrectly inspects kwargs for
explicit parameters (content, content_base64, source_path) which will never be
in kwargs; update the logic in the containing function (the block using
has_any_content) to consider both bound parameters and kwargs — e.g., check the
explicit parameters content, content_base64, source_path as well as
kwargs.get(...) — so calls like filename=None, content="..." are detected
correctly and the truncation message is only returned when none of those sources
contain content; keep the existing ErrorResponse behavior and session_id usage.
🪄 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: 7fff82ee-f957-414c-ade2-98dba411aae3

📥 Commits

Reviewing files that changed from the base of the PR and between b319c26 and e54f092.

📒 Files selected for processing (6)
  • autogpt_platform/backend/backend/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_test.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). (11)
  • GitHub Check: check API types
  • GitHub Check: Seer Code Review
  • GitHub Check: end-to-end tests
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: type-check (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: type-check (3.12)
  • GitHub Check: type-check (3.13)
  • GitHub Check: Check PR Status
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
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/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_test.py
autogpt_platform/{backend,autogpt_libs}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_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/copilot/sdk/tool_adapter_test.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_test.py
🧠 Learnings (24)
📓 Common learnings
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12632
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-01T04:17:43.495Z
Learning: In autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py (PR `#12632`, commit 12ae03c), the per-tool `BaseTool.read_only` property approach was removed. Instead, `readOnlyHint=True` (via `ToolAnnotations`) is applied unconditionally to ALL tools — including side-effect tools like `bash_exec` and `write_workspace_file` — to enable fully parallel dispatch by the Anthropic SDK/CLI. Do not flag tools with mutating operations (e.g. save_to_path, write operations) for having `readOnlyHint=True`; this is intentional and E2E validated (3x bash_exec(sleep 3) completed in 3.3s vs 9s sequential).
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.
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: 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: 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: 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: 12632
File: autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py:530-535
Timestamp: 2026-04-01T04:17:38.279Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py`, the `ToolAnnotations(readOnlyHint=True)` annotation (stored as `_PARALLEL_ANNOTATION`) is intentionally applied to ALL registered MCP tools — including E2B write/edit tools (e.g., `write_file`, `edit_file`). This is a parallel-dispatch hint to the Claude Agent SDK CLI, not a semantic read-only contract. The `_READ_ONLY_E2B_TOOLS` set was dead code and was removed in commit `12ae03c`; the constant was renamed from `_READONLY_ANNOTATION` to `_PARALLEL_ANNOTATION` in commit `c88ca88` to avoid confusion. Do not flag this as a correctness issue.
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.
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-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/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
📚 Learning: 2026-03-09T10:50:43.907Z
Learnt from: Bentlybro
Repo: Significant-Gravitas/AutoGPT PR: 0
File: :0-0
Timestamp: 2026-03-09T10:50:43.907Z
Learning: Repo: Significant-Gravitas/AutoGPT — File: autogpt_platform/backend/backend/blocks/llm.py
For xAI Grok models accessed via OpenRouter, the API returns `null` for `max_completion_tokens`. The convention in this codebase is to use the model's context window size as the `max_output_tokens` value in ModelMetadata. For example, Grok 3 uses 131072 (128k) and Grok 4 uses 262144 (256k). Do not flag these as incorrect max output token values.

Applied to files:

  • autogpt_platform/backend/backend/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.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/copilot/prompting.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/copilot/prompting.py
📚 Learning: 2026-04-01T04:17:41.600Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12632
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-01T04:17:41.600Z
Learning: When reviewing AutoGPT Copilot tool implementations, accept that `readOnlyHint=True` (provided via `ToolAnnotations`) may be applied unconditionally to *all* tools—even tools that have side effects (e.g., `bash_exec`, `write_workspace_file`, or other write/save operations). Do **not** flag these tools for having `readOnlyHint=True`; this is intentional to enable fully-parallel dispatch by the Anthropic SDK/CLI and has been E2E validated. Only flag `readOnlyHint` issues if they conflict with the established `ToolAnnotations` behavior (e.g., missing/incorrect propagation relative to the intended annotation mechanism).

Applied to files:

  • autogpt_platform/backend/backend/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_test.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/copilot/prompting.py
📚 Learning: 2026-04-03T11:14:16.378Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/transcript_builder.py:30-34
Timestamp: 2026-04-03T11:14:16.378Z
Learning: In `autogpt_platform/backend/backend/copilot/transcript_builder.py` (and its re-export shim at `sdk/transcript_builder.py`), `TranscriptEntry.parentUuid` is typed `str` (not `str | None`) and root entries use `parentUuid=""` (empty string) to match the canonical `_messages_to_transcript` JSONL format. `_parse_entry`, `append_user`, and `append_assistant` all coerce `None` to `""`. Do NOT flag `parentUuid=""` as incorrect — it is the correct root marker. This was fixed in PR `#12623`, commit b753cb7d0b.

Applied to files:

  • autogpt_platform/backend/backend/copilot/prompting.py
📚 Learning: 2026-04-08T17:28:23.422Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/AGENTS.md:0-0
Timestamp: 2026-04-08T17:28:23.422Z
Learning: Applies to autogpt_platform/backend/**/*.py : 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)

Applied to files:

  • autogpt_platform/backend/backend/copilot/prompting.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/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.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/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_test.py
📚 Learning: 2026-03-04T08:04:35.881Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12273
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:216-220
Timestamp: 2026-03-04T08:04:35.881Z
Learning: In the AutoGPT Copilot backend, ensure that SVG images are not treated as vision image types by excluding 'image/svg+xml' from INLINEABLE_MIME_TYPES and MULTIMODAL_TYPES in tool_adapter.py; the Claude API supports PNG, JPEG, GIF, and WebP for vision. SVGs (XML text) should be handled via the text path instead, not the vision path.

Applied to files:

  • autogpt_platform/backend/backend/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_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/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_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/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_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/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_test.py
📚 Learning: 2026-04-01T04:17:38.279Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12632
File: autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py:530-535
Timestamp: 2026-04-01T04:17:38.279Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py`, the `ToolAnnotations(readOnlyHint=True)` annotation (stored as `_PARALLEL_ANNOTATION`) is intentionally applied to ALL registered MCP tools — including E2B write/edit tools (e.g., `write_file`, `edit_file`). This is a parallel-dispatch hint to the Claude Agent SDK CLI, not a semantic read-only contract. The `_READ_ONLY_E2B_TOOLS` set was dead code and was removed in commit `12ae03c`; the constant was renamed from `_READONLY_ANNOTATION` to `_PARALLEL_ANNOTATION` in commit `c88ca88` to avoid confusion. Do not flag this as a correctness issue.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
📚 Learning: 2026-03-04T12:19:39.243Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12279
File: autogpt_platform/backend/backend/copilot/tools/base.py:184-188
Timestamp: 2026-03-04T12:19:39.243Z
Learning: In autogpt_platform/backend/backend/copilot/tools/, ensure that anonymous users always pass user_id=None to tool execution methods. The anon_ prefix (e.g., anon_123) is used only for PostHog/analytics distinct_id and must not be used as an actual user_id. Use a simple truthiness check on user_id (e.g., if user_id: ... else: ... or a dedicated is_authenticated flag) to distinguish anonymous from authenticated users, and review all tool execution call sites within this directory to prevent accidentally forwarding an anon_ user_id to tools.

Applied to files:

  • autogpt_platform/backend/backend/copilot/tools/workspace_files.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_test.py
📚 Learning: 2026-03-31T14:22:26.566Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12622
File: autogpt_platform/backend/backend/copilot/tools/agent_search.py:223-236
Timestamp: 2026-03-31T14:22:26.566Z
Learning: In files under autogpt_platform/backend/backend/copilot/tools/, ensure agent graph enrichment uses the typed Pydantic model `backend.data.graph.Graph` for `AgentInfo.graph` (i.e., `Graph | None`), not `dict[str, Any]`. When enriching with graph data (e.g., `_enrich_agents_with_graph`), prefer calling `graph_db().get_graph(graph_id, version=None, user_id=user_id)` directly to retrieve the typed `Graph` object rather than routing through JSON conversions like `get_agent_as_json()` / `graph_to_json()`.

Applied to files:

  • autogpt_platform/backend/backend/copilot/tools/workspace_files.py
  • autogpt_platform/backend/backend/copilot/tools/workspace_files_test.py
📚 Learning: 2026-02-27T10:45:55.700Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12213
File: autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunMCPTool/helpers.tsx:23-24
Timestamp: 2026-02-27T10:45:55.700Z
Learning: As of PR `#12213`, MCP tool response types (MCPToolsDiscoveredResponse, MCPToolOutputResponse) are defined in openapi.json and frontend code in autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunMCPTool/helpers.tsx uses the generated types from `@/app/api/__generated__/`. Other tools like RunBlock still use inline TypeScript interfaces (e.g., BlockDetailsResponse) for SSE stream payloads that are not included in openapi.json schemas. The pattern is tool-specific: use generated types when available in openapi.json, use inline types only when the payload schema is truly SSE-stream-only and not exposed via OpenAPI.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter.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/copilot/sdk/tool_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.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/copilot/sdk/tool_adapter.py
📚 Learning: 2026-02-04T16:49:42.490Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-04T16:49:42.490Z
Learning: Applies to autogpt_platform/backend/**/test/**/*.py : Use snapshot testing with '--snapshot-update' flag in backend tests when output changes; always review with 'git diff'

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
📚 Learning: 2026-03-26T07:00:03.405Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12574
File: autogpt_platform/backend/backend/copilot/sdk/transcript.py:980-990
Timestamp: 2026-03-26T07:00:03.405Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/transcript.py`, `_rechain_tail` intentionally rewrites `parentUuid` for **all** tail entries (not just the first), because a single assistant turn can span multiple consecutive JSONL entries sharing the same `message.id` (e.g., a thinking entry + a tool_use entry). Their original `parentUuid` values may reference entries that were absorbed into the compressed prefix, so sequential rechaining of the entire tail is required to maintain a valid parent→child graph. The test `test_chains_multiple_tail_entries` validates this: the second tail entry's `parentUuid` is rewritten from its original value to the uuid of the first tail entry.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py
📚 Learning: 2026-04-08T17:28:23.422Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/AGENTS.md:0-0
Timestamp: 2026-04-08T17:28:23.422Z
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/copilot/sdk/tool_adapter_test.py

Comment on lines +939 to +1029
class TestPartialTruncationDetection:
"""When the API truncates a write tool call mid-JSON, `content` may survive
but `file_path` is lost. The wrapper must detect this and return actionable
guidance instead of letting the call through to fail with an opaque error.
"""

@pytest.mark.asyncio
async def test_write_file_partial_truncation_detected(self):
"""write_file with content but no file_path returns truncation error."""

async def fake_tool_fn(_args: dict) -> dict:
raise AssertionError("Should not be called")

wrapper = _make_truncating_wrapper(
fake_tool_fn,
"write_file",
input_schema={"required": ["file_path", "content"]},
)
result = await wrapper({"content": "some data"})
assert result["isError"] is True
text = result["content"][0]["text"]
assert "truncated" in text
assert "file_path" in text

@pytest.mark.asyncio
async def test_edit_file_partial_truncation_detected(self):
"""edit_file with new_string but no file_path returns truncation error."""

async def fake_tool_fn(_args: dict) -> dict:
raise AssertionError("Should not be called")

wrapper = _make_truncating_wrapper(
fake_tool_fn,
"edit_file",
input_schema={"required": ["file_path", "old_string", "new_string"]},
)
result = await wrapper({"new_string": "replacement"})
assert result["isError"] is True
text = result["content"][0]["text"]
assert "truncated" in text

@pytest.mark.asyncio
async def test_write_file_with_file_path_passes_through(self):
"""write_file with file_path present should NOT trigger truncation guard."""
called = False

async def fake_tool_fn(_args: dict) -> dict:
nonlocal called
called = True
return {
"content": [{"type": "text", "text": "ok"}],
"isError": False,
}

normal_session = MagicMock()
normal_session.dry_run = False
set_execution_context(user_id="test", session=normal_session, sandbox=None, sdk_cwd="/tmp/test") # type: ignore[arg-type]

wrapper = _make_truncating_wrapper(
fake_tool_fn,
"write_file",
input_schema={"required": ["file_path", "content"]},
)
result = await wrapper({"file_path": "/home/user/f.txt", "content": "data"})
assert called
assert result["isError"] is False

@pytest.mark.asyncio
async def test_non_write_tool_not_affected(self):
"""Partial truncation guard only applies to write/edit tools."""
called = False

async def fake_tool_fn(_args: dict) -> dict:
nonlocal called
called = True
return {
"content": [{"type": "text", "text": "ok"}],
"isError": False,
}

normal_session = MagicMock()
normal_session.dry_run = False
set_execution_context(user_id="test", session=normal_session, sandbox=None, sdk_cwd="/tmp/test") # type: ignore[arg-type]

wrapper = _make_truncating_wrapper(
fake_tool_fn,
"bash_exec",
input_schema={"required": ["command"]},
)
await wrapper({"content": "some data"})
assert called

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.

🛠️ Refactor suggestion | 🟠 Major

Please add a write_workspace_file case to this suite.

The new wrapper logic also branches on write_workspace_file, but this class only exercises write_file and edit_file. A write_workspace_file(content=...) regression test would have caught the current file_path/filename mix-up immediately.

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

In `@autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.py` around
lines 939 - 1029, Add a new test in TestPartialTruncationDetection that mirrors
test_write_file_partial_truncation_detected but uses the "write_workspace_file"
tool name so the truncation guard for workspace writes is exercised; use
_make_truncating_wrapper(fake_tool_fn, "write_workspace_file",
input_schema={"required": ["filename", "content"]}) and call wrapper({"content":
"some data"}) asserting result["isError"] is True and the returned text mentions
"truncated" and the missing "filename"/"file_path" as appropriate; also add a
positive case like test_write_file_with_file_path_passes_through but for
write_workspace_file to ensure calls with "filename" pass through and the fake
tool is invoked.

Comment on lines +530 to +550
# Partial truncation: the API cut the JSON mid-way — `content` or
# `new_string` survived but `file_path` (emitted later) was lost.
if (
tool_name in ("write_file", "write_workspace_file", "edit_file")
and args
and "file_path" not in args
and ("content" in args or "new_string" in args)
):
logger.warning(
"[MCP] %s: partial truncation detected — file_path missing",
tool_name,
)
return _mcp_error(
f"Your {tool_name} call was truncated (file_path missing). "
"The content was too large for a single tool call. "
"Write in chunks: use bash_exec with "
"'cat > file << EOF ... EOF' for the first section, "
"'cat >> file << EOF ... EOF' to append subsequent "
"sections, then reference the file with "
"@@agptfile:/path/to/file if needed."
)

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.

⚠️ Potential issue | 🔴 Critical

This guard blocks valid write_workspace_file(content=...) calls.

write_workspace_file does not have a file_path argument, so this condition treats every normal inline-content call as “partial truncation” and short-circuits before the tool runs. It also misses the other valid payload inputs for that tool (content_base64 and source_path).

Proposed fix
-        if (
-            tool_name in ("write_file", "write_workspace_file", "edit_file")
-            and args
-            and "file_path" not in args
-            and ("content" in args or "new_string" in args)
-        ):
+        truncation_checks = {
+            "write_file": ("file_path", {"content"}),
+            "edit_file": ("file_path", {"new_string"}),
+            "write_workspace_file": (
+                "filename",
+                {"content", "content_base64", "source_path"},
+            ),
+        }
+        required_key, payload_keys = truncation_checks.get(tool_name, (None, set()))
+        if (
+            required_key
+            and args
+            and required_key not in args
+            and any(key in args for key in payload_keys)
+        ):
             logger.warning(
-                "[MCP] %s: partial truncation detected — file_path missing",
+                "[MCP] %s: partial truncation detected — %s missing",
                 tool_name,
+                required_key,
             )
             return _mcp_error(
-                f"Your {tool_name} call was truncated (file_path missing). "
+                f"Your {tool_name} call was truncated ({required_key} missing). "
                 "The content was too large for a single tool call. "
                 "Write in chunks: use bash_exec with "
                 "'cat > file << EOF ... EOF' for the first section, "
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/copilot/sdk/tool_adapter.py` around lines
530 - 550, The guard treats any call to write_workspace_file with inline content
as truncated because write_workspace_file doesn't use file_path; update the
conditional in the truncation-detection block to either exclude
"write_workspace_file" from the tool_name tuple or to check allowed payload keys
for that tool (e.g., treat write_workspace_file as valid if args contains
"content" or "content_base64" or "source_path"); ensure tools that truly need
file_path (write_file, edit_file) still trigger the _mcp_error when "file_path"
is missing, and keep the existing logger.warning and _mcp_error behavior for
those cases.

Comment on lines +638 to +666
async def test_empty_args_returns_actionable_truncation_message(
self, ephemeral_dir
):
"""Calling write with no args at all should mention cat > and cat >>."""
write_tool = WriteWorkspaceFileTool()
result = await write_tool._execute(
user_id="test-user",
session=make_session(),
filename=None, # type: ignore[arg-type]
)
assert isinstance(result, ErrorResponse)
assert "truncated" in result.message.lower()
assert "cat >" in result.message
assert "cat >>" in result.message
assert "source_path" in result.message

@pytest.mark.asyncio
async def test_missing_filename_with_content_gives_simple_error(
self, ephemeral_dir
):
"""When content is present but filename is missing, it's not truncation —
just a missing required field."""
write_tool = WriteWorkspaceFileTool()
result = await write_tool._execute(
user_id="test-user",
session=make_session(),
filename=None, # type: ignore[arg-type]
content="some content",
)

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.

🛠️ Refactor suggestion | 🟠 Major

Remove the new # type: ignore suppressors here.

These tests add two # type: ignore[arg-type] escapes for filename=None. Please model this path in the types instead — e.g. let _execute() accept filename: str | None for malformed/truncated calls, or route the call through a typed helper — so the tests can stay suppression-free.

As per coding guidelines, "Do not use linter suppressors — no # type: ignore, # noqa, # pyright: ignore; fix the type/code instead".

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

In `@autogpt_platform/backend/backend/copilot/tools/workspace_files_test.py`
around lines 638 - 666, The tests currently use "# type: ignore[arg-type]" for
filename=None; instead update the implementation rather than suppressing types:
change WriteWorkspaceFileTool._execute (and any interface it implements) to
accept filename: str | None (or overloads/typed helper e.g.,
_execute_with_optional_filename) and handle the None case (truncated/malformed
vs missing required field) internally, then remove the type ignores from tests;
ensure any callers or type stubs of WriteWorkspaceFileTool._execute are updated
to the new signature so mypy/pyright no longer requires suppressors.

@codecov

codecov Bot commented Apr 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.15%. Comparing base (b319c26) to head (e54f092).

Additional details and impacted files
@@           Coverage Diff           @@
##              dev   #12749   +/-   ##
=======================================
  Coverage   63.14%   63.15%           
=======================================
  Files        1811     1811           
  Lines      130463   130532   +69     
  Branches    14260    14262    +2     
=======================================
+ Hits        82376    82432   +56     
- Misses      45495    45507   +12     
- Partials     2592     2593    +1     
Flag Coverage Δ
platform-backend 74.63% <80.00%> (+<0.01%) ⬆️
platform-frontend-e2e 28.11% <ø> (-0.04%) ⬇️

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

Components Coverage Δ
Platform Backend 74.63% <80.00%> (+<0.01%) ⬆️
Platform Frontend 23.77% <ø> (-0.02%) ⬇️
AutoGPT Libs ∅ <ø> (∅)
Classic AutoGPT 28.43% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@majdyz

majdyz commented Apr 12, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #12750 which includes the unified file tools (Read/Write/Edit) with the schema-level truncation fix. All changes from this PR are covered by #12750.

@majdyz majdyz closed this Apr 12, 2026
@github-project-automation github-project-automation Bot moved this from 🆕 Needs initial review to ✅ Done in AutoGPT development kanban Apr 12, 2026
@majdyz
majdyz deleted the fix/write-tool-truncation branch April 12, 2026 06:32
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 size/l

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

1 participant