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
- 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.
- Mid-session, edit
task_plan.md: set all four ### Phase blocks to **Status:** complete and append the close marker <!-- pwf: closed -->.
- 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:
:315 — pi.on("agent_end", async (_event, ctx) => {
:318 — const 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:
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.
- 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.
Summary
After a plan is marked complete on all phases and closed (
<!-- pwf: closed -->), theagent_endhook keeps emitting a "Task incomplete" nag as afollowUpuser message across many subsequent turns:Expected vs actual
readPlanStatus()reportsclosed === trueandcompletePhases === totalPhases, theagent_endhandler hits itsif (status.closed) returnearly-out and never emits a "Task incomplete" nag — at the latest on the turn immediately following the close.3/4) did not match the actual on-disk state (4/4). It only stopped after a session restart.Reproduction
/plan-execute) with e.g. 4 phases, initially incomplete (2/4 complete). Theagent_endnag fires correctly while incomplete.task_plan.md: set all four### Phaseblocks to**Status:** completeand append the close marker<!-- pwf: closed -->.Evidence (verified with the extension's own exported functions)
Calling the extension's own
resolvePlanPaths()+readPlanStatus()+isPlanIncomplete()against the live session cwd returns:Per-phase classification (replicating
classifyPhaseStatus): all four### Phaseblocks 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::315—pi.on("agent_end", async (_event, ctx) => {:318—const 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)if (status.closed) { ...; return; }early-out:401— the nag string that kept firingGiven 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:
ctx.cwdatagent_enddoes not point at the directory whose plan was closed, soreadPlanStatus(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.agent_endblock (so it isn't gated by thestatus.closedcheck).A logged/observable
ctx.cwdand the resolvedplanIdat the moment of firing would immediately disambiguate.Environment
1.4.3(vendored as@repo/pi-agent-ext-planning-with-files).planning/<plan-id>/(gitignored working memory); close marker<!-- pwf: closed -->Suggested fix direction
Either (a) ensure the
agent_endnag is gated purely on the freshly-readstatus(and confirmctx.cwdalways 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.