From d9aa91ed2697a6b981373fdb5a5ada288e230f95 Mon Sep 17 00:00:00 2001 From: Benjamin Borbe Date: Sun, 16 Aug 2026 15:55:56 +0200 Subject: [PATCH] report in-progress count in the incomplete-subtasks refusal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guard refuses on pending > 0 OR inProgress > 0, but the message interpolated pending alone. A task blocked purely by [/] in-progress items therefore failed with 'incomplete subtasks: 0 pending' — a count of zero offered as the reason for refusing. Message now reads 'incomplete subtasks: N pending, M in-progress'. Adds a regression test for the [/]-only case asserting the in-progress count appears. The existing pending-only assertion still matches, since the new text keeps the pending clause as its prefix; vault_dispatcher tests stub their own error strings and are unaffected. --- CHANGELOG.md | 4 ++++ pkg/ops/complete.go | 7 ++++++- pkg/ops/complete_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bed184e..35a9706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ Please choose versions by [Semantic Versioning](http://semver.org/). - fix: scenario 005's "no approval turn" assertion could not distinguish a real failure from two unrelated causes, and two walks on 2026-08-16 duly contradicted each other. A prompt between resume and invocation has three sources: the classification bug (what the scenario tests), the trust-folder gate (a precondition the walk never proved), and — new since v0.110.0/v0.111.1 — the Phase 5.5 permission-mode precheck, which now runs for *every* task. The assertion now names all three and tells the walker to identify which before recording FAIL, plus a dedicated `🔐 Permission mode:` assertion: the fixture's only command is `/vault-cli:next-task`, so Phase 5.5's own rule ("emit nothing … do not warn on read-only or docs-only tasks") means seeing it is a Phase 5.5 trigger defect to file separately, not a classification failure. Also hardened two things the contradicting walks got wrong: the trust precondition must be *proven in the environment the walk runs in* (a fresh tmux / isolated HOME / sub-agent shell does not inherit it), and `claude_session_id` is now marked necessary-but-not-sufficient — one walk reported PASS from it alone after losing the scrollback. `PLUGIN_VER` is now required in the result, since this flow's behavior moved twice in a single day. +## Unreleased + +- fix: `task complete`'s refusal message reported only the pending count, so a task blocked purely by in-progress `[/]` items failed with the literal text `incomplete subtasks: 0 pending` — a count of zero given as the reason for refusing. The guard has always refused on `pending > 0 || inProgress > 0`, but the message interpolated `pending` alone. It now reads `incomplete subtasks: N pending, M in-progress`. Misleading at exactly the moment someone is trying to close out work correctly; found alongside the missing `--force` flag (v0.111.0), first reported 2026-08-11. + ## v0.111.1 - fix: the permission-mode precheck added in v0.110.0 never fired for ops tasks. It sat inside Phase 5, which is gated on the code-task heuristic (`fix|implement|refactor|add|bug|deploy|build`), so a task whose entire body is `kubectl delete` skipped Phase 5 wholesale and the precheck with it — every `decommission` / `renew` / `rebuild` / `migrate` task was silently excluded, which is exactly the ops work that needs the switch. Moved to its own Phase 5.5 that runs for all tasks and keys on the task's own commands. Caught by exercising v0.110.0 on "Decommission MinIO on Hell" the same day it shipped. diff --git a/pkg/ops/complete.go b/pkg/ops/complete.go index 0e7cf24..5a56010 100644 --- a/pkg/ops/complete.go +++ b/pkg/ops/complete.go @@ -180,7 +180,12 @@ func (c *completeOperation) checkSubtaskCompletion( Completed: completed, Total: total, } - return result, true, errors.Errorf(ctx, "incomplete subtasks: %d pending", pending) + return result, true, errors.Errorf( + ctx, + "incomplete subtasks: %d pending, %d in-progress", + pending, + inProgress, + ) } // handleRecurringTask handles completion of a recurring task. diff --git a/pkg/ops/complete_test.go b/pkg/ops/complete_test.go index 1ef1ee2..44c68a1 100644 --- a/pkg/ops/complete_test.go +++ b/pkg/ops/complete_test.go @@ -118,6 +118,30 @@ var _ = Describe("CompleteOperation", func() { }) }) + Context("task blocked only by in-progress checkboxes", func() { + BeforeEach(func() { + task = domain.NewTask( + map[string]any{"status": "todo"}, + domain.FileMetadata{Name: taskName}, + domain.Content("# Tasks\n\n- [x] done\n- [/] one\n- [/] two\n- [/] three\n"), + ) + mockTaskStorage.FindTaskByNameReturns(task, nil) + force = false + }) + + It("returns error", func() { + Expect(err).NotTo(BeNil()) + }) + + It("names the in-progress count, not just pending", func() { + Expect(err.Error()).To(ContainSubstring("0 pending, 3 in-progress")) + }) + + It("does not write the task", func() { + Expect(mockTaskStorage.WriteTaskCallCount()).To(Equal(0)) + }) + }) + Context("task with incomplete checkboxes", func() { BeforeEach(func() { task = domain.NewTask(