fix(copilot): multi-layer defence against Write tool truncation errors - #12749
fix(copilot): multi-layer defence against Write tool truncation errors#12749majdyz wants to merge 1 commit into
Conversation
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
WalkthroughThis 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
🔍 PR Overlap DetectionThis check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early. 🟢 Low Risk — File Overlap OnlyThese 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: |
There was a problem hiding this comment.
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_contentnever sees the bound content args.
content,content_base64, andsource_pathare explicit parameters, so they are not present inkwargs. On Line 797 this makeshas_any_contentfalse even for calls likefilename=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
📒 Files selected for processing (6)
autogpt_platform/backend/backend/copilot/prompting.pyautogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.pyautogpt_platform/backend/backend/copilot/tools/workspace_files.pyautogpt_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: Usepoetry run ...command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies likeopenpyxl
Use absolute imports withfrom 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 — avoidhasattr/getattr/isinstancefor 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%sfor deferred interpolation indebuglog statements for efficiency; use f-strings elsewhere for readability (e.g.,logger.debug("Processing %s items", count)vslogger.info(f"Processing {count} items"))
Sanitize error paths by usingos.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
Usetransaction=Truefor Redis pipelines to ensure atomicity on multi-step operations
Usemax(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.pyautogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.pyautogpt_platform/backend/backend/copilot/tools/workspace_files.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.pyautogpt_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.pyautogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.pyautogpt_platform/backend/backend/copilot/tools/workspace_files.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.pyautogpt_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.pynaming 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
UseAsyncMockfromunittest.mockfor async functions in tests
When writing tests, use Test-Driven Development (TDD): write failing tests marked with@pytest.mark.xfailbefore implementation, then remove the marker once the implementation is complete
When creating snapshots in tests, usepoetry run pytest path/to/test.py --snapshot-update; always review snapshot changes withgit diffbefore committing
Files:
autogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.pyautogpt_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.pyautogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter.pyautogpt_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.pyautogpt_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.pyautogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.pyautogpt_platform/backend/backend/copilot/tools/workspace_files.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.pyautogpt_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.pyautogpt_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.pyautogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.pyautogpt_platform/backend/backend/copilot/tools/workspace_files.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.pyautogpt_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.pyautogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.pyautogpt_platform/backend/backend/copilot/tools/workspace_files.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.pyautogpt_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.pyautogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.pyautogpt_platform/backend/backend/copilot/tools/workspace_files.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.pyautogpt_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.pyautogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.pyautogpt_platform/backend/backend/copilot/tools/workspace_files.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.pyautogpt_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.pyautogpt_platform/backend/backend/copilot/sdk/e2b_file_tools.pyautogpt_platform/backend/backend/copilot/tools/workspace_files.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter_test.pyautogpt_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.pyautogpt_platform/backend/backend/copilot/sdk/tool_adapter.pyautogpt_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.pyautogpt_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.pyautogpt_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.pyautogpt_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
| 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 |
There was a problem hiding this comment.
🛠️ 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.
| # 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." | ||
| ) |
There was a problem hiding this comment.
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.
| 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", | ||
| ) |
There was a problem hiding this comment.
🛠️ 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 Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Summary
Writetool (added toSDK_DISALLOWED_TOOLS) so all file writes route through our MCPwrite_file/write_workspace_filetools where we control schema validation and error messagestool_adapter.py— whencontent/new_stringis present butfile_pathis missing, return actionable chunking guidance instead of letting the call fail with an opaque Ajv errorwrite_file— if the write succeeds but was dangerously close to the truncation threshold, suggest chunking for next timeworkspace_files.pytruncation error message with step-by-step instructions (cat >,cat >>,source_path)Root cause
When the LLM generates a very large
contentargument 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-df9dff9ecfc62bb9fb0d-05bd-4194-8f3f-88fbe3d8b9658226d82e-bf9c-48e3-826f-80048d0fb7b4Test plan
tool_adapter_test.py(4 cases: write_file truncated, edit_file truncated, write_file with file_path passes through, non-write tool not affected)ruff checkandpyrightpass on all modified files