Skip to content

fix(cli): resolve plan_exit to the plan file actually saved - #11896

Merged
johnnyeric merged 1 commit into
mainfrom
johnnyeric/plan-exit-file-resolution
Jul 3, 2026
Merged

fix(cli): resolve plan_exit to the plan file actually saved#11896
johnnyeric merged 1 commit into
mainfrom
johnnyeric/plan-exit-file-resolution

Conversation

@johnnyeric

@johnnyeric johnnyeric commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Issue

Fixes #11859

Context

Plan mode had two related bugs on finalize: plan_exit reported a filename that didn't match what was actually written, and the "implement in new session?" follow-up prompt would silently fail to appear.

Root cause: plan_exit fell back to Session.plan()'s randomly-slugged path whenever the model omitted an explicit path. That was fine while the reminder pinned an exact filename, but a later change (#11170) moved to agent-chosen descriptive filenames without updating the fallback - so whenever the model omitted path, plan_exit reported a file that was never written. The follow-up flow read a plan file at that same wrong path, got an empty read, and silently skipped the prompt - same root cause, different symptom.

Implementation

plan_exit and the follow-up flow now verify the plan file exists on disk instead of trusting a guessed path:

  • PlanFile.resolve() now rejects a path that exists but isn't a file (e.g. a directory), in addition to its existing containment check.
  • New PlanFile.locate() recovers the real file in tiers: exact target → newest file matching the session's generated-name pattern (<session-created>-*.md) → the last markdown file written by a planning agent (covers custom architect-slug agents too, via a caller-supplied agent name). This is intentionally narrow - a false-positive match in this fallback would finalize the wrong file, so it only looks at write/edit tool calls from plan/architect-agent turns.
  • plan_exit now fails with an actionable error ("write the plan file first, or pass the exact path") instead of silently succeeding with a nonexistent path.
  • Filesystem.stat was hardened to never throw (it previously only suppressed ENOENT; ENOTDIR/EACCES could throw and crash the new verification path) - every existing caller already treated its result as optional, so this only removes latent crash paths.

This diff went through several rounds of adversarial code review; each round found and fixed real issues (a too-loose write-history fallback, a directory passing as a valid plan path, an agent-name filter that excluded custom architect slugs) before converging.

Screenshots / Video

plan.mov

How to Test

Manual/local verification

  • bun test test/kilocode/plan-file.test.ts test/kilocode/plan-followup.test.ts test/kilocode/plan-exit-detection.test.ts — 54 passing (agent)
  • bunx tsc --noEmit -p packages/opencode — clean (agent)
  • bun turbo typecheck (full monorepo, via pre-push hook) — 20/20 tasks passing (agent)

Reviewer test steps

  1. Start Plan mode, let the agent write a plan under a descriptive filename it chose itself, and call plan_exit without an explicit path - the "Plan is ready at X" link should point at the real file.
  2. At the follow-up prompt, choose "Start new session" — the new session's plan-file reference should point at the same real file.
  3. Delete the plan file mid-planning and call plan_exit - expect a visible "Plan file not found" tool error (not a silent success or a stuck session).

Checklist

  • Issue linked above, or exception explained
  • Tests/verification described
  • Screenshots/video included for visual changes, or marked N/A
  • Changeset considered for user-facing changes
  • I personally reviewed the diff and can explain the changes, including any AI-assisted work.

@johnnyeric
johnnyeric force-pushed the johnnyeric/plan-exit-file-resolution branch 3 times, most recently from 0a5fe8d to e9d94c2 Compare July 3, 2026 11:14
@johnnyeric
johnnyeric marked this pull request as ready for review July 3, 2026 11:31

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9d94c2d36

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/opencode/src/kilocode/plan-file.ts Outdated
plan_exit fell back to Session.plan()'s generated slug path whenever
the model omitted an explicit path, which no longer matched the
agent-chosen filename plan mode now instructs. That mismatch caused
both the wrong filename shown after "Plan is ready" and a silently
missing implement-next-session prompt (issue #11859).

plan_exit and the follow-up flow now verify the plan file exists,
recovering it via a session-timestamp glob or the plan agent's last
markdown write when the exact path is missing, and fail loudly with
actionable guidance when nothing was written.

The failure message also names an explicit path parameter that got
rejected (outside the project, or a directory), instead of silently
substituting a freshly-guessed filename that never matches what the
model actually wrote. This only affects wording of the final error:
Session.plan() and locate()'s recovery tiers still run first, so a
rejected path that's actually the canonical non-git plans dir (which
sits outside the project boundary by design) still recovers via the
timestamp glob rather than failing outright.
@johnnyeric
johnnyeric force-pushed the johnnyeric/plan-exit-file-resolution branch from e9d94c2 to 08a9d69 Compare July 3, 2026 11:44
@kilo-code-bot

kilo-code-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The saved()/written() tiered recovery logic in plan-file.ts correctly bounds fallback lookups to the project or canonical plan directory (via containsPath/Filesystem.contains), the previously flagged stale-fallback issue is fixed with a regression test (fresh file now wins the mtime comparison), and the filesystem.ts stat() hardening is scoped with kilocode_change markers and consistent with how all 8 existing callers already treat the result as optional. New tests exercise the real implementation against a tmp filesystem/git repo rather than mocks.

Files Reviewed (6 files)
  • .changeset/plan-file-finalize.md
  • packages/opencode/src/kilocode/plan-file.ts
  • packages/opencode/src/kilocode/plan-followup.ts
  • packages/opencode/src/kilocode/tool/plan.ts
  • packages/opencode/src/util/filesystem.ts
  • packages/opencode/test/kilocode/plan-file.test.ts

Reviewed by claude-sonnet-5-20260630 · Input: 34 · Output: 19.1K · Cached: 1.3M

Review guidance: REVIEW.md from base branch main

@johnnyeric
johnnyeric merged commit c36c293 into main Jul 3, 2026
27 checks passed
@johnnyeric
johnnyeric deleted the johnnyeric/plan-exit-file-resolution branch July 3, 2026 12:54
@johnnyeric johnnyeric mentioned this pull request Jul 6, 2026
3 tasks
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
…#11896)

plan_exit fell back to Session.plan()'s generated slug path whenever
the model omitted an explicit path, which no longer matched the
agent-chosen filename plan mode now instructs. That mismatch caused
both the wrong filename shown after "Plan is ready" and a silently
missing implement-next-session prompt (issue Kilo-Org#11859).

plan_exit and the follow-up flow now verify the plan file exists,
recovering it via a session-timestamp glob or the plan agent's last
markdown write when the exact path is missing, and fail loudly with
actionable guidance when nothing was written.

The failure message also names an explicit path parameter that got
rejected (outside the project, or a directory), instead of silently
substituting a freshly-guessed filename that never matches what the
model actually wrote. This only affects wording of the final error:
Session.plan() and locate()'s recovery tiers still run first, so a
rejected path that's actually the canonical non-git plans dir (which
sits outside the project boundary by design) still recovers via the
timestamp glob rather than failing outright.
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.

Plan-Mode Bug

2 participants