Summary
plannotator annotate ... --gate --json emits a structured decision on stdout, but every decision exits successfully and there is no atomic no-clobber result-file contract. Automation cannot use process status as an approval gate, and detached callers must redirect stdout to a destination that can be observed while empty or partial.
Reproduction
- Run
plannotator annotate plan.md --gate --json.
- Send annotations instead of approving.
- Observe a valid
{"decision":"annotated","feedback":"..."} record on stdout.
- Observe that the process exits
0.
- Redirect stdout to a file and observe that Plannotator provides no atomic publication or no-clobber guarantee for readers.
Observed behavior
The direct JSON transport distinguishes approved, annotated, and dismissed decisions only in stdout content. Exit status remains 0, so fail-closed callers must add their own JSON parser. Shell redirection controls the destination and may expose an empty or partial file while the command is still running.
Expected behavior
Direct annotate should offer optional strict flags that require --gate --json:
--require-approval should exit 0 only for approval while still publishing valid annotated and dismissed JSON before a nonzero exit.
--result-file <path> should publish the exact newline-terminated stdout record through an atomic readiness boundary.
The result path should resolve from the invocation working directory, require an existing parent and absent destination, use a private same-directory temporary file, flush and close it, and publish with an atomic no-clobber operation. Unsupported hard links or a concurrent destination must fail closed without an overwrite or copy fallback.
This contract is atomic no-clobber publication, not crash durability: file data is synced before hard-link publication, but the parent directory is not synced.
Existing plaintext, JSON, and hook-native output must remain byte-for-byte compatible when strict flags are absent. The strict flags must not be added to shared OpenCode or Pi slash-command parsing, and --hook must remain unchanged and incompatible with them.
Root cause
Direct annotate flag handling extracts only --gate, --json, and --hook. After waitForDecision(), the command uses the legacy emitter and unconditionally exits 0. There is no separate strict serializer, exit policy, completion coordinator, or secure result-file publisher.
Impact
Foreground automation cannot distinguish requested revisions from approval using exit status alone. Detached automation has no safe readiness boundary for a result file and risks accepting stale, pre-existing, partial, or overwritten data as the current review decision.
Proposed scope
- Add direct-only parsing and help for
--require-approval and --result-file <path>.
- Require
--gate --json; reject --hook, non-annotate commands, missing values, and duplicate strict options.
- Keep the legacy annotate emitter unchanged and use a separate strict serializer only when a strict option is present.
- Add an injectable completion coordinator that publishes the result file, waits for stdout completion, and exits in that order.
- Publish result files through a
0600 same-directory temporary file, file sync, close, hard link, and temporary unlink.
- Reject existing destinations before server startup and rely on hard-link no-clobber semantics for publication races.
- Add focused parser, serializer, coordinator, ordering, failure, and filesystem tests.
- Document the strict direct-gate contract without changing OpenCode, Pi, or hook behavior.
Prior art and overlap
A refreshed read-only GitHub search on 2026-07-20 found no issue or pull request for --require-approval or --result-file.
Contribution
I have a tested direct-CLI implementation with focused, integrated, and live atomic-publication coverage ready to contribute. About to push the PR => Created and ready #1091
Summary
plannotator annotate ... --gate --jsonemits a structured decision on stdout, but every decision exits successfully and there is no atomic no-clobber result-file contract. Automation cannot use process status as an approval gate, and detached callers must redirect stdout to a destination that can be observed while empty or partial.Reproduction
plannotator annotate plan.md --gate --json.{"decision":"annotated","feedback":"..."}record on stdout.0.Observed behavior
The direct JSON transport distinguishes approved, annotated, and dismissed decisions only in stdout content. Exit status remains
0, so fail-closed callers must add their own JSON parser. Shell redirection controls the destination and may expose an empty or partial file while the command is still running.Expected behavior
Direct
annotateshould offer optional strict flags that require--gate --json:--require-approvalshould exit0only for approval while still publishing valid annotated and dismissed JSON before a nonzero exit.--result-file <path>should publish the exact newline-terminated stdout record through an atomic readiness boundary.The result path should resolve from the invocation working directory, require an existing parent and absent destination, use a private same-directory temporary file, flush and close it, and publish with an atomic no-clobber operation. Unsupported hard links or a concurrent destination must fail closed without an overwrite or copy fallback.
This contract is atomic no-clobber publication, not crash durability: file data is synced before hard-link publication, but the parent directory is not synced.
Existing plaintext, JSON, and hook-native output must remain byte-for-byte compatible when strict flags are absent. The strict flags must not be added to shared OpenCode or Pi slash-command parsing, and
--hookmust remain unchanged and incompatible with them.Root cause
Direct annotate flag handling extracts only
--gate,--json, and--hook. AfterwaitForDecision(), the command uses the legacy emitter and unconditionally exits0. There is no separate strict serializer, exit policy, completion coordinator, or secure result-file publisher.Impact
Foreground automation cannot distinguish requested revisions from approval using exit status alone. Detached automation has no safe readiness boundary for a result file and risks accepting stale, pre-existing, partial, or overwritten data as the current review decision.
Proposed scope
--require-approvaland--result-file <path>.--gate --json; reject--hook, non-annotate commands, missing values, and duplicate strict options.0600same-directory temporary file, file sync, close, hard link, and temporary unlink.Prior art and overlap
A refreshed read-only GitHub search on 2026-07-20 found no issue or pull request for
--require-approvalor--result-file.Contribution
I have a tested direct-CLI implementation with focused, integrated, and live atomic-publication coverage ready to contribute. About to push the PR => Created and ready #1091