Write Tool Rejects Writing Plans To Default Plan Location ../../.claude/plans/
Bug
When plan mode is active, the system prompt instructs the model to write its plan to ~/.claude/plans/<slug>.md. The model calls the Write tool with that absolute path. The harness computes path.relative(cwd, targetPath), which produces a ../../.claude/plans/… relative path. This relative path is then validated by the bundled ignore library, which rejects any path containing ../ because allowRelativePaths defaults to false.
The Write tool throws:
path should be a `path.relative()`d string, but got "../../.claude/plans/valiant-popping-sketch.md"
The binary’s own plan-file code writes to the same ~/.claude/plans/ directory via direct fs.writeFileSync and succeeds — only the model’s Write tool is affected.
Root Cause (pseudocode)
planDir = settings.plansDirectory
? resolve(projectRoot, settings.plansDirectory)
: join(CLAUDE_CONFIG_DIR, "plans") // ~/.claude/plans/
// Write tool path validation (simplified):
rel = path.relative(cwd, targetPath) // "../../.claude/plans/slug.md"
ignoreInstance.test(rel) // <-- throws RangeError
The ignore instance is constructed with allowRelativePaths defaulting to false. Its isNotRelative check rejects any path starting with ../:
isNotRelative(path):
return /^\.\.\//.test(path) // true for ../../.claude/plans/...
_test(path):
if strictPathCheck and isNotRelative(path):
throw RangeError("path should be a path.relative()'d string")
Additional Finding: plansDirectory Workaround Is Also Broken
Setting plansDirectory to an absolute path like ~/.claude/plans does not work because the resolver enforces that the resolved path is within the project root:
resolved = path.resolve(projectRoot, plansDirectory)
if !resolved.startsWith(projectRoot):
log(Error("plansDirectory must be within project root"))
fallback to ~/.claude/plans/ // the broken default
The only accepted value is a relative path inside the project (e.g., ".claude/plans"). However, even with this set, the system prompt still tells the model to write to ~/.claude/plans/ — it does not respect the plansDirectory setting. So the workaround fails at two levels:
- Absolute paths outside project root are silently rejected (falls back to broken default)
- The system prompt ignores
plansDirectoryand always points to~/.claude/plans/
Expected Behavior
The Write tool should accept the plan file path that the system prompt told the model to use. Either:
- Exempt plan file paths from the
../check - Pass
allowRelativePaths: trueto the ignore instance used for write validation - Resolve the plan file to an absolute path before the relative-path check
- Have the system prompt respect
plansDirectorywhen set
Reproduction
cd /any/project(any directory that is not~/.claude)- Start Claude Code, enter plan mode
- Model attempts to write plan to
~/.claude/plans/<slug>.md - Write tool throws
RangeError - Setting
"plansDirectory": "/home/user/.claude/plans"silently falls back to the same broken path - Setting
"plansDirectory": ".claude/plans"is accepted by the resolver but the system prompt still points to~/.claude/plans/
Version
Observed in 2.1.81. Plans directory resolution is identical from 2.1.75–2.1.81 (checked via binary analysis). The regression may have been introduced when the Write tool started routing all paths through the ignore library’s validation.
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗