Skip to content

Closed & 4/4-complete plan still emits 'Task incomplete (N/M)' nags for multiple turns #203

Description

@ziyu4huang

Summary

After a plan is marked complete on all phases and closed (<!-- pwf: closed -->), the agent_end hook keeps emitting a "Task incomplete" nag as a followUp user message across many subsequent turns:

[planning-with-files] Task incomplete (3/4 phases done). Update progress.md with what was done, then read task_plan.md and continue remaining phases.

Expected vs actual

  • Expected: once readPlanStatus() reports closed === true and completePhases === totalPhases, the agent_end handler hits its if (status.closed) return early-out and never emits a "Task incomplete" nag — at the latest on the turn immediately following the close.
  • Actual: the nag persisted for ~6+ turns after the plan was closed and fully complete, and the count it reported (3/4) did not match the actual on-disk state (4/4). It only stopped after a session restart.

Reproduction

  1. Start an active plan (via /plan-execute) with e.g. 4 phases, initially incomplete (2/4 complete). The agent_end nag fires correctly while incomplete.
  2. Mid-session, edit task_plan.md: set all four ### Phase blocks to **Status:** complete and append the close marker <!-- pwf: closed -->.
  3. Continue interacting normally. The "Task incomplete (3/4…)" followUps keep arriving for several turns despite the plan being closed + complete.

Evidence (verified with the extension's own exported functions)

Calling the extension's own resolvePlanPaths() + readPlanStatus() + isPlanIncomplete() against the live session cwd returns:

active plan  = context-diet-phase4
status       = 4/4 complete | closed = true | isPlanIncomplete = false

Per-phase classification (replicating classifyPhaseStatus): all four ### Phase blocks match rule 1 (/\*\*Status:\*\*\s*complete\b/i). No .attestation / .active_plan / content-cache files exist under .planning/. So the on-disk plan is unambiguously closed + complete, yet the nag still fired.

Suspected cause / code refs

src/runtime.ts:

  • :315pi.on("agent_end", async (_event, ctx) => {
  • :318const status = readPlanStatus(ctx.cwd); (a fresh read each turn — there is no in-memory cache of plan content, so a stale-cache explanation is ruled out)
  • the agent_end body has an if (status.closed) { ...; return; } early-out
  • :401 — the nag string that kept firing

Given the read is fresh every turn and the disk state is closed+complete, the nag should be impossible via this code path. The most likely explanations:

  1. ctx.cwd at agent_end does not point at the directory whose plan was closed, so readPlanStatus(ctx.cwd) resolves a different/older plan (e.g. a worktree or a parent-dir plan) that is still incomplete. The agent's working directory can differ from where the user edited the plan.
  2. The followUp message is injected by a path other than this agent_end block (so it isn't gated by the status.closed check).

A logged/observable ctx.cwd and the resolved planId at the moment of firing would immediately disambiguate.

Environment

  • planning-with-files 1.4.3 (vendored as @repo/pi-agent-ext-planning-with-files)
  • pi coding agent on Apple Silicon
  • plan lives in .planning/<plan-id>/ (gitignored working memory); close marker <!-- pwf: closed -->

Suggested fix direction

Either (a) ensure the agent_end nag is gated purely on the freshly-read status (and confirm ctx.cwd always equals the plan's dir), or (b) add a one-line log of { ctx.cwd, planId, closed, completePhases/totalPhases } right before the nag so this is diagnosable in the wild. Happy to help validate a fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions