Repository navigation
Conversation
… is in t3_project_delete let a thread delete the project it runs in. With force: true the cascade deletes every thread in the project, the caller included, while the call is still running, so the tool never returns and the agent never learns what it did. The MCP handler now refuses that call with invalid_request when the calling thread's project is the target. Client callers, other projects, the force rule and the service cascade are unchanged.
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — The handler now prevents an agent from deleting the project containing its own thread. This narrowly scoped safeguard runs before deletion and preserves existing access checks and other deletion behavior. You can add or adjust custom eligibility rules. Learn more. |
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Note 🤖 Opus 5.5 replying on behalf of Victor. Closing this to bring my open PRs down under the new limit. The branch stays on my fork if it is useful. |
Problem
t3_project_deletelets an agent delete the project its own thread is in. Withforce: true, the service deletes every thread in the project before the project itself. The calling thread is one of them, so its run ends in the middle of the tool call. The call never returns, and the agent never learns what it did. Any parent thread in the same project, such as the orchestrator that delegated the work, is deleted too.I hit this for real. An agent capturing screenshots against its own dev server called
t3_project_listandt3_project_delete, thinking they reached the dev server. They act on the T3 install the agent runs in, so it deleted that install's working project withforce: true. Its own thread, its parent orchestrator and every other thread in the project (788 in total) went with it, and the tool call ended as interrupted.Steps on
main:t3_project_deletewith this thread'sprojectIdandforce: true.Change
The
t3_project_deletehandler now reads the caller from the access check it already runs. When the calling thread is in the target project, it refuses withinvalid_request: "This thread is in that project, so deleting the project would delete this thread too."ProjectServiceis never called.Nothing else changes. Clients with no calling thread, threads in other projects, the
forcerule and the service's cascade work as before. The Settings flow confirms the thread count before deleting;t3 project removeremains a user-run CLI command with its existing--forcebehavior, so both keep their behavior. The tool's description and schema are unchanged.Scope and approval
This is a very small, focused fix of an obvious bug: an MCP tool call deletes its own caller and never returns. The check sits in the MCP handler, beside the existing caller access checks, because only an MCP call has a calling thread.
ProjectServicekeeps its deletion behavior, as #10563 intended. It touches one handler and its existing test, with no contract or client change.Verification
The existing
t3_project_deletecase inapps/server/src/mcp/toolkits/project/handlers.test.tsnow makes three calls: a client delete (still deletes and discards the clone record), a live full-access thread in the target project (refused, nothing deleted), and a thread in another project (still deletes).On
main(vp test run apps/server/src/mcp/toolkits/project/handlers.test.ts):With this change:
Tests 13 passed (13).vp run check:changed 836b86a5d6b7738193656b9c638318afe6caef35passes (format, lint, knip 1/2, knip 2/2, GraphQL, typecheck for 3 packages, and tests for 2 files; exit 0).For the screenshots, a scripted ACP agent runs as the provider of an isolated dev server. It calls the real
t3_project_deletetool with its own thread's project, in a test project with two other threads.The test project with two threads, before the agent's thread starts:
Before, on
main(836b86a5d6): the call never returns, and the project and all of its threads are gone, the agent's own thread included.After (
ab707df885): the call is refused, and the project and all three threads stay.Not checked: desktop and mobile render the same server result and were not run separately. A thread can still delete another project that holds its parent thread; this change covers only the caller's own project.
Generated with Claude Opus 5.5 and GPT-6.1 Terra and GPT-6.1 Sol in T3 Code Nightly