-
Notifications
You must be signed in to change notification settings - Fork 3
plat 371
PLAT-371 — CLI adapters overwrote and deleted a project's own instruction files; overlapping Crew/Code sessions collided
| Coordination | Value |
|---|---|
| State | merged to main in all three repos (2026-09-30), not deployed
|
| Date | 2026-09-29 |
| Owner | security-sandbox |
| Related | PLAT-296 (private folders for workflow Builder/Run), PLAT-364 (CLI confinement), design: project instruction files, plan: workflows on the shared folder |
Crew design update (2026-09-30): Crew now uses separate Run/Builder prompts
and skills in private runtimes with project/ linked to its real files. This
supersedes the shared Crew folder/prompt decision below; Code retains the safe
shared-folder leases. See the current design.
The per-message reader notice and enforcement remain. Kernel tests confirm that
Run cannot write through the link and that ungranted link targets stay denied;
every authenticated CLI still needs live qualification before deployment.
Crew and Code chats, schedules, triggers and bots all run with the project folder as the CLI's working directory, and several sessions can be live in it at once. Two layers wrote into that folder and cleaned up carelessly:
-
The adapters wrote the session prompt to
CLAUDE.md(Claude),AGENTS.md(Codex, Muse),.pi/APPEND_SYSTEM.md(Pi) and.cursor/rules/mlp-system.mdc(Cursor). They overwrote a file the project already had, and deleted it when the first session ended. Restore was off by default and, with overlapping sessions, would have restored another session's file. Reproduced with the real Claude adapter functions: the user'sCLAUDE.mdwas gone after one session. -
mcpagent's startup cleanup (cleanupInactiveCodingAgentProjectArtifacts) ranRemoveAllon.claude,.cursor,.pi,.codexand.agentsof the project for every provider that was not the active one. Reproduced: starting a Codex turn deleted a test project's.claude/settings.json,.claude/commands/deploy.md,.cursor/rules/team.mdcand.pi/settings.json. Any Code project with committed CLI configuration was exposed, silently, on any turn.
Also: the prompt differs between an owner and a reader (or guest) of a Crew, so one shared instruction file could not hold both.
- Never delete anything we did not create; delete only what we created.
-
AGENTS.mdis the single project-instructions file for Claude and Codex (Claude Code reads it natively; verified 2.1.284). We stop writingCLAUDE.md. - A Crew has two roles, owner (Builder) and reader (Run). A guest call is a reader. The owner does not need a way to chat in Run mode.
- One shared prompt per folder. The reader role is a short block in front of each message; tools and folder guards enforce it; refusals repeat it.
- Crew and Code first, tested; workflows follow separately (plan doc).
-
multi-llm-provider-gopkg/projectfile(branchproject-files-safe): a marked block added to the file, a per-path session count, idempotent lease tokens, stale block cleanup, an owned-file lease for uniquely named files. Claude (nowAGENTS.md), Codex, Muse, Pi and the Cursor rules file use it. Projected skills carry.agentworks-managed; an existing folder of the same name is taken over and marked so skill updates keep reaching old projects; other skills untouched. -
mcpagent(branchproject-files-safe): startup and on-close cleanup remove only marked skills, our block, and generated config recognised by content. No folder is removed. - This repo (branch
crew-code-private-cli-folder):crew_session_mode.go([AGENTWORKS SESSION]block on the normal turn and on live input, stripped from history, submissions and native transcripts), the reader/guest prompt removed from the system prompt, a short shared-prompt paragraph, and write refusals in a read-only session that namesubmit_crew_suggestion/submit_workflow_suggestion. - Bridge refusals (2026-09-30): a call to a mutating tool a reader was never given
(
crewReaderDeniedTools) now returns the read-only message instead of "not found" (refuseReaderDeniedTool, both/tools/customroutes), and a shell command that fails in a read-only session with "Operation not permitted" / "Permission denied" / "Read-only file system" gets the same message appended to stderr (withReadOnlyShellHint). Hidden tools stay hidden from the catalog.
Cursor wrote several files into .cursor/ (mcp.json, cli.json, hooks.json, the
hook script) with restore off by default, deleted a project's cli.json at startup,
and created a temporary .git marker that the structured path removed
unconditionally. Now every config write is a counted, always-restoring lease
(projectfile.AcquireOwnedLease*): a project's own file is restored byte-for-byte by
the last session and overlapping sessions keep each other's; a project's cli.json is
never deleted (a session with bridge tools replaces it with its allowlist and it is
restored afterwards); the .git marker is shared by sessions, removed by the last one,
and only if it is still the directory we created (a real repository is never touched).
Provider commit 667aeec. Tests: cursorcli_project_files_safety_test.go.
With the session prompt carried only by the AGENTS.md block (project-instruction-only
mode), a Claude that ignores AGENTS.md runs every session with no system prompt and no
error. Claude Code 2.1.233 does not read AGENTS.md; 2.1.284 and 2.1.285 do (tested on
RTS with the service token: AGENTS.md-only and a CLAUDE.md control). RTS had both a
root-owned /usr/bin/claude 2.1.233 (and pi 0.84.2) from first provisioning and the
managed ~/.local/bin copies kept current by the deploy and the cli-update timer. The
service PATH puts ~/.local/bin first, so the managed copy won, but the old one was a
silent fallback (the deploy script already records it running for hours on 2026-09-25).
- RTS (2026-09-30):
sudo npm uninstall -g @anthropic-ai/claude-code @earendil-works/pi-coding-agent; the service now resolves claude 2.1.285, pi 0.87.1 and cursor-agent only from~/.local/bin. The local Mac already has one copy of each CLI. Excellence was not reachable to check. - Provisioning (
template.yaml,repair-bootstrap.sh) no longer installs claude or pi system-wide. - Deploy preflight (
build-and-activate.sh): fails ifclaudeon the service PATH is not the managed one, warns on any system copy, and fails if this claude does not readAGENTS.md(codeword probe; a capped account only warns). - Adapter (provider
3036cff): in instruction-only mode a claude older than 2.1.284, or whose version cannot be read, gets the prompt through--system-prompt-fileand noAGENTS.mdcarrier (a duplicate at worst, never a gap).
-
Slack/WhatsApp channel routes. A Slack channel route with a read grant runs as the Crew's
owner but read-only (
botRouteProfileAccessForRequestreturnsReadunless the grant isowner). The reader notice used to fire only when the caller was not the owner, so it missed these turns even though tools and guards treated them as read-only.crewSessionModeForTurnnow follows whether the TURN is read-only, and a channel turn's notice says the conversation is a shared chat channel. -
No terminal for read-only access. Native terminal typing goes straight to the CLI and skips
the notice, so the terminal is refused for a caller looking at their own session with read-only
access (a Crew reader, a read-only login, a read-only channel route) and kept for owners and
editors. Enforced in
canAccessTerminalSession(every terminal route), with a 403 "only available to owners and editors" from the main-terminal route, whichMainAgentTerminalshows once before returning to the chat. The mark is set per turn (SetSessionReadOnlyAccess,access == WorkflowAccessRead), so a guest call on an owner's session is not affected. An owner or editor cannot be put in read-only mode, so their terminal is untouched. Verified live: reader 403 on the metadata and stream routes, owner 200, cross-user 404.
Unit tests in all three repos. Real CLIs: Claude 2.1.284 and Codex 0.159 both read
the user's own AGENTS.md together with our block. Server-level runs on isolated
local instances (real Claude and Codex through /api/agent-profiles/*/query):
- Code, single-user: user's
AGENTS.md,.claude/settings.json,.claude/commands/deploy.md,.cursor/rules/team.mdc,.pi/settings.jsonintact during and after; two Claude chats plus a Codex chat overlapping; one shared block; after the last session endedAGENTS.mdwas byte-for-byte the original. - Bridge refusals, live, using the reader session's own credentials:
create_project_scheduleandset_workflow_secretreturned the read-only message withsubmit_crew_suggestion;list_project_schedulesstill worked;echo hello > notes.mdwas blocked by the sandbox and now says why;lswas unaffected;notes.mdwas not created. (The model itself refuses before trying, so the messages are what a CLI sees if it does try.) - Guest call, multi-user, real Claude: reader1's Crew ("Caller") used
call_functionto ask owner1's Crew ("Support")ask. The Support turn ran as a guest with the reader role (its CLI pane got the[AGENTWORKS SESSION]block; its tool gate registered 30 tools; the caller's pane got no block), answered from its ownAGENTS.md, and the answer came back to the caller as an[AUTO-NOTIFICATION]result ({"answer": "... OSPREY-3 ..."}). The guest did not callreturn_function_result; its final answer is what is returned (the turn's own instruction says so), so the removed guest prompt was not needed for that path. - Crew, multi-user (owner + reader in one Crew): owner answered from the Crew's own
AGENTS.md; reader was refused a file write and offered a suggestion; the mode block reached only the reader's CLI, on both the first message and a live-input follow-up; no stored chat message or submission contains the block; the file was restored when both ended. - RTS project folders (read-only scan 2026-09-29): five folders; the CLI folders held only our own projected skills; no user files were lost there. Excellence was not reachable for the same scan.
- agy refuses a second session with a different tool mode in one folder.
- Skill cleanup is not session-counted (needed before workflows share a folder).
- Old unmarked skill folders of a provider not in use stay as clutter.
- Deploy: nothing is deployed.
mcpagentstill pins provider68688ec(the Cursor fix is in667aeec; this repo pins the newer one and builds against local copies). Themcpagentcheckout has another session's uncommitted work, so its pin bump was left for that session to land.
TestSalesCrewCatalogHasInstallableRoles, TestAssembledWorkflowPrompt (24 KB
ceiling), Muse exec-lane MCP mount, a flaky Claude paste-chip pane test.
Auto-synced from docs/ on main. Edit there, not here.