Commit fa10fc7
authored
🤖 fix: delete plan file on Start Here + read-only exec (#1116)
### What
- When clicking **Start Here** on a `propose_plan` tool call, we now
delete the plan file on disk (new + legacy paths) and clear plan file
tracking state.
- The plan file is now **readable in exec mode** via `file_read` (we
always pass `planFilePath`, and plan path detection is mode-agnostic).
- The plan file is now explicitly **read-only outside plan mode**,
including for SSH runtimes.
### Tests
- `make static-check`
- `bun test src/node/services/tools/file_read.test.ts
src/node/services/tools/file_edit_operation.test.ts
src/node/runtime/SSHRuntime.test.ts`
---
<details>
<summary>📋 Implementation Plan</summary>
# 🤖 Plan: Delete plan file on “Start Here” + make plan file readable
(read-only) in Exec mode
## Problem
1. **Plan file is left behind after using “Start Here” on a
`propose_plan` tool call.**
- The chat history is replaced, but
`~/.mux/plans/<project>/<workspace>.md` remains on disk, confusing later
sessions (stale/duplicate plan).
2. **Switching from Plan → Exec currently blocks reading the plan
file.**
- `file_read` only exempts the plan file from `cwd`-restriction in
**plan mode**; in exec mode the plan file path resolves outside the
workspace and is rejected.
## Goals
- When the user clicks **Start Here** on a `propose_plan` tool call,
delete the plan file from disk (new + legacy paths) and clear plan file
tracking state.
- In **exec mode**, allow the agent to **read** the plan file via
`file_read`, but **never write/modify** it (treat as read-only outside
plan mode).
- Preserve existing safety: agents still can’t read arbitrary files
outside the workspace; only the plan file is exempt.
- Support **local + SSH runtimes**.
## Non-goals
- Changing where plan files are stored or the plan file naming scheme.
- Auto-deleting the plan file on every “Start Here” action (only when
invoked from `propose_plan`).
---
## Recommended approach (A): Add `deletePlanFile` option to
`workspace.replaceChatHistory`
**Net LoC (product code): ~120–180**
### Why this approach
- Single round-trip: “Start Here” + plan cleanup happens in one backend
call.
- Keeps deletion scoped to the UI that knows the intent (`propose_plan`
Start Here).
- Avoids fragile heuristics (e.g., inferring intent from message ID
prefix).
### Implementation steps
#### 1) Allow plan file *read* in exec mode (but keep edits blocked)
1. **Pass `planFilePath` in tool config in all modes**
- File: `src/node/services/aiService.ts`
- Change tool config from:
- `planFilePath: mode === "plan" ? planFilePath : undefined`
- to `planFilePath: planFilePath` (always set)
- Rationale: tools need to know the canonical plan path even in exec
mode.
2. **Refactor plan path detection helper(s)**
- File: `src/node/services/tools/fileCommon.ts`
- Add a helper that checks whether `targetPath` equals the configured
plan file path **regardless of mode**, using `runtime.resolvePath` for
tilde/absolute equivalence.
- Keep the existing plan-mode semantics available for edit tools.
- Suggested shape:
- `isConfiguredPlanFilePath(targetPath, config)` → ignores
`config.mode`, requires `config.planFilePath`.
- `isPlanFilePathInPlanMode(targetPath, config)` → `config.mode ===
"plan" && isConfiguredPlanFilePath(...)`.
3. **Update `file_read` to allow reading the plan file in any mode**
- File: `src/node/services/tools/file_read.ts`
- Replace the current exemption check:
- `if (!(await isPlanFilePath(filePath, config))) {
validatePathInCwd(...) }`
- With:
- `if (!(await isConfiguredPlanFilePath(filePath, config))) {
validatePathInCwd(...) }`
4. **(Defensive) Make plan file explicitly read-only outside plan mode**
- Files:
- `src/node/services/tools/file_edit_operation.ts`
- `src/node/services/tools/file_edit_insert.ts`
- Add an early check:
- If `isConfiguredPlanFilePath(filePath, config)` and `config.mode !==
"plan"`, return a clear error like:
- `Plan file is read-only outside plan mode: <path>`
- This prevents future regressions where someone might accidentally add
a read-exemption to edits too.
#### 2) Delete the plan file when “Start Here” is used on `propose_plan`
5. **Extend ORPC schema for `replaceChatHistory`**
- File: `src/common/orpc/schemas/api.ts`
- Add an optional flag:
- `deletePlanFile: z.boolean().optional()`
- Keep default behavior unchanged when omitted.
6. **Plumb the flag through the ORPC router**
- File: `src/node/orpc/router.ts`
- Pass the flag to the workspace service:
- `replaceHistory(workspaceId, summaryMessage, { deletePlanFile })`.
7. **Implement deletion in `WorkspaceService.replaceHistory`**
- File: `src/node/services/workspaceService.ts`
- Update signature to accept an optional options object.
- If `deletePlanFile === true`:
- Delete both:
- `getPlanFilePath(metadata.name, metadata.projectName)`
- `getLegacyPlanFilePath(workspaceId)`
- Use the existing local/SSH-safe deletion technique already used in
`truncateHistory`:
- `rm -f <quotedNewPath> <quotedLegacyPath>` via `runtime.exec(...)`
- Clear plan file tracking state:
- `this.sessions.get(workspaceId)?.clearFileState()`
- **Refactor**: extract the duplicated plan deletion logic currently
embedded in `truncateHistory` into a private helper (e.g.,
`deletePlanFilesForWorkspace(...)`) and call it from both places.
8. **Update frontend “Start Here” call site for plans**
- Files:
- `src/browser/hooks/useStartHere.ts`
- `src/browser/components/tools/ProposePlanToolCall.tsx`
- Extend `useStartHere(...)` to accept optional `replaceChatHistory`
options.
- In `ProposePlanToolCall`, pass `{ deletePlanFile: true }`.
- Keep `AssistantMessage` usage unchanged (no flag), so ordinary “Start
Here” does not delete the plan.
---
## Alternative approach (B): Add a separate `workspace.deletePlanFile`
endpoint
**Net LoC (product code): ~140–220**
### Summary
- Keep `replaceChatHistory` unchanged.
- Add a new endpoint that deletes plan files (new + legacy) + clears
file tracking state.
- `ProposePlanToolCall` would call `replaceChatHistory(...)` and then
`deletePlanFile(...)`.
### Pros / cons
- **Pros**: avoids touching an existing API signature.
- **Cons**: two round-trips; harder to make the behavior feel atomic;
more UI error-handling complexity.
---
## Test plan
### Unit tests
1. **`file_read` can read plan file in exec mode**
- File: `src/node/services/tools/file_read.test.ts`
- Create a temp plan file outside `cwd`.
- Configure tool with `mode: "exec"`, `planFilePath: <planPath>`.
- Assert `file_read` succeeds for the plan file and still rejects other
outside-cwd paths.
2. **Plan file is not editable outside plan mode**
- Files:
- `src/node/services/tools/file_edit_operation.test.ts`
- (optionally) `src/node/services/tools/file_edit_insert.test.ts`
- With `mode: "exec"` and `planFilePath` set, attempt to edit plan file
path.
- Expect the explicit “read-only outside plan mode” error.
### Integration tests (IPC)
3. **`replaceChatHistory(deletePlanFile: true)` deletes plan file**
- Add a test near existing plan tests (e.g.,
`tests/ipc/planCommands.test.ts` or a new
`tests/ipc/startHerePlanCleanup.test.ts`).
- Create workspace, create plan file at the expected location, call
`replaceChatHistory` with the flag.
- Assert:
- Plan file no longer exists.
- Subsequent `getPlanContent` returns `success: false`.
---
## Manual verification checklist
- Plan mode: create/edit plan file, call `propose_plan`.
- Click **Start Here** on the plan tool call:
- Chat history is replaced.
- Plan file is deleted from `~/.mux/plans/<project>/<workspace>.md`.
- Switch to exec mode without using Start Here:
- `file_read` can read the plan file.
- `file_edit_*` attempting to modify the plan file fails (read-only).
---
## Rollout / compatibility notes
- This change is backward compatible: the new `deletePlanFile` flag is
optional.
- Existing “Start Here” on normal assistant messages is unaffected.
- Plan file deletion includes both new and legacy paths to handle
migrated workspaces.
</details>
---
_Generated with `mux`_
Signed-off-by: Thomas Kosiewski <tk@coder.com>1 parent 0bdcd3b commit fa10fc7
File tree
13 files changed
+218
-44
lines changed- src
- browser
- components/tools
- hooks
- common/orpc/schemas
- node
- orpc
- runtime
- services
- tools
- tests/ipc
13 files changed
+218
-44
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
223 | 223 | | |
224 | 224 | | |
225 | 225 | | |
226 | | - | |
| 226 | + | |
| 227 | + | |
227 | 228 | | |
228 | 229 | | |
229 | 230 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
19 | | - | |
| 20 | + | |
| 21 | + | |
20 | 22 | | |
21 | 23 | | |
22 | 24 | | |
| |||
52 | 54 | | |
53 | 55 | | |
54 | 56 | | |
| 57 | + | |
55 | 58 | | |
56 | 59 | | |
57 | 60 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
272 | 272 | | |
273 | 273 | | |
274 | 274 | | |
| 275 | + | |
| 276 | + | |
275 | 277 | | |
276 | 278 | | |
277 | 279 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
409 | 409 | | |
410 | 410 | | |
411 | 411 | | |
412 | | - | |
| 412 | + | |
| 413 | + | |
413 | 414 | | |
414 | 415 | | |
415 | 416 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
416 | 416 | | |
417 | 417 | | |
418 | 418 | | |
419 | | - | |
420 | | - | |
421 | | - | |
422 | | - | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
423 | 428 | | |
424 | 429 | | |
425 | 430 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1154 | 1154 | | |
1155 | 1155 | | |
1156 | 1156 | | |
1157 | | - | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
1158 | 1160 | | |
1159 | | - | |
| 1161 | + | |
1160 | 1162 | | |
1161 | 1163 | | |
1162 | 1164 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
36 | 39 | | |
37 | | - | |
38 | | - | |
| 40 | + | |
| 41 | + | |
39 | 42 | | |
40 | 43 | | |
41 | 44 | | |
42 | 45 | | |
43 | 46 | | |
44 | | - | |
| 47 | + | |
45 | 48 | | |
46 | 49 | | |
47 | | - | |
48 | | - | |
| 50 | + | |
| 51 | + | |
49 | 52 | | |
50 | 53 | | |
51 | 54 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
67 | 75 | | |
68 | 76 | | |
69 | 77 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
216 | 216 | | |
217 | 217 | | |
218 | 218 | | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
219 | 249 | | |
220 | 250 | | |
221 | 251 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
59 | 68 | | |
60 | 69 | | |
61 | 70 | | |
| |||
0 commit comments