Skip to content

Conversation

@ThomasK33
Copy link
Member

fix: use canonical resolved plan file path in Plan Mode

This fixes a mismatch where Plan Mode instructed the model to use ~/.mux/plans/..., but in dev/test runs mux may resolve ~/.mux to a different on-disk directory (e.g. ~/.mux-dev). If the model converted the tilde path into an OS-home absolute path, file tools would reject it as “outside the workspace directory”.

Changes:

  • Use readPlanFile(...).path (canonical resolved path) for:
    • Plan Mode system prompt instructions
    • exec-mode plan file hint
    • plan→exec transition context injection
    • tool config planFilePath

Validation:

  • make typecheck
  • make static-check

📋 Implementation Plan

Fix: Plan Mode plan-file path rejected when using absolute path

Problem

Plan Mode tells the model to read/write a plan file that lives outside the workspace (under mux home). When the model uses an absolute path (e.g. /Users/.../.mux/plans/...), file tools reject it as “outside the workspace directory”.

This happens because the tools only bypass the workspace sandbox for the configured plan file, and the plan file identity check is currently brittle across different “equivalent” plan paths.

Recommended approach (A): Always use the canonical resolved plan path everywhere

Goal: ensure the path shown to the model, the path used by tools (config.planFilePath), and the path used by propose_plan are all the same canonical absolute path.

Changes

  1. In aiService.ts, stop using the raw planFilePath string built from runtime.getMuxHome().
  2. After readPlanFile(...), use the returned canonical resolved path (planResult.path) as:
    • the value passed into getPlanModeInstruction(...) / getPlanFileHint(...)
    • the value passed into injectModeTransition(...) (when including plan path)
    • the value passed into tool configuration (planFilePath in getToolsForModel(...))
  3. Keep the existing raw planFilePath only where needed for legacy migration logic (if any), but do not expose it to the model or tool config.

Why this works

  • readPlanFile() already calls runtime.resolvePath() and returns a canonical absolute path.
  • File tools (file_read, file_edit_*) treat config.planFilePath as the single escape hatch to allow access outside the workspace. If we standardize it to the canonical absolute path, the “is this the plan file?” check stays stable, even when the model uses absolute paths.

Validation

  • Run Plan Mode in a dev workspace.
  • Confirm:
    • file_read of the injected plan file path succeeds (or returns ENOENT if missing), without “restricted to workspace” errors.
    • file_edit_insert can create the plan file.
    • propose_plan reads the same file.

Net LoC estimate (product code): ~10–20 LoC.


Alternative (B): Make plan-file identity check tolerant to “mux home” aliasing

Instead of changing what path is injected/configured, make isPlanFilePath() consider both:

  • the resolved runtime path for ~/.mux/... (mux home, which may differ in dev)
  • the OS-homedir expansion path for ~/.mux/...

This reduces user-visible confusion but increases ambiguity (two possible plan files) and can cause propose_plan to read a different file than the model edited.

Net LoC estimate (product code): ~20–40 LoC.


Alternative (C): Prompt-only mitigation

Update Plan Mode instructions to explicitly say: “Use the plan file path exactly as provided; do not convert ~ to /Users/....”

This is easy but relies on model compliance and does not fix the root mismatch.

Net LoC estimate (product code): ~3–6 LoC.

Notes / root cause details
  • The workspace sandbox error comes from validatePathInCwd() (file tool restriction).
  • The bypass only triggers when isPlanFilePath(targetPath, config) matches config.planFilePath.
  • In dev, mux uses a “mux home” concept (often ~/.mux-dev) and some code paths treat ~/.mux as an alias for mux home. Models may expand ~/.mux to /Users/<name>/.mux, which is not necessarily the same directory mux is using.
  • Standardizing on the canonical resolved path from readPlanFile() removes this ambiguity.

_Generated with mux • Model: openai:gpt-5.2 • Thinking: xhigh • Cost: $

Change-Id: I13a056f4c8e08d539025f115249f890d27605a40
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: Idaad68c25245509a2fe7797a542a5049d58dfefe
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I6e3c679cffd4f1a29418efbc0fe5a61bedb584c6
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant