feat(flow): hold a suspend inhibitor for the run, inside the CLI#256
Merged
Conversation
A `pdca flow` — a batch, or a high-difficulty bundle on a strong Do model — can run for hours unattended; if the host auto-suspends on idle, suspend pauses every process and cuts the cycle off mid-run. Hold a suspend inhibitor for the command's lifetime from inside the CLI: on the real `flow` entry, re-exec under systemd-inhibit (Linux) / caffeinate (macOS), guarded against double-wrap by PDCA_FLOW_INHIBITED, so every invocation path — direct, detached, screen — is covered by one implementation. Advisory only (idle:sleep, never shutdown); opt out with --no-inhibit / PDCA_NO_INHIBIT for CI/containers; a one-line notice when no inhibitor is available. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 270baf9921
ℹ️ 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".
…attery (#244 review) Two Codex findings: 1. `python -m pdca_harness.cli flow …` has argv[0] = the cli.py file path; forwarding that as the inhibitor's command made systemd-inhibit/caffeinate try to exec a non-executable .py file, so the re-exec failed before flow started on hosts with an inhibitor. Add _reexec_command: for a module/script invocation (argv[0] ends .py / __main__) rebuild the child as `sys.executable -m pdca_harness.cli …`; an installed console script is execable and forwarded as-is. 2. macOS `caffeinate -s` is the system-sleep assertion valid only on AC power; the idle assertion `-i` is what holds on battery — the common unattended-laptop case. Use `caffeinate -i -s` to mirror systemd's idle:sleep. Tests: module-invocation re-exec + console-script passthrough, and the caffeinate flags. make check → 487 tests pass. Live: `python -m pdca_harness.cli flow --rehearse` under a recorder now re-invokes `python3 -m pdca_harness.cli …` and reaches the CLI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #244.
Problem
A
pdca flow— a batch, or a high-difficulty bundle on a strong Do model — can run for hours, unattended. If the host auto-suspends on idle, suspend pauses every process and interrupts the cycle mid-run. Reported from getwyrd/wyrd-pdca, which was adding a localscripts/flowwrapper as a stopgap.Change — inside the installed
pdcacommand, notmakepdca flowis the single choke point for a long unattended run, so the inhibitor lives inside that command; every path (direct,make, script, detached) is then covered by one implementation.In
src/pdca_harness/cli.py:_suspend_inhibitor_argv(argv, env)— the pure decision (unit-testable, no exec): returns the wrapper argv orNone. Skips when already wrapped (PDCA_FLOW_INHIBITED), opted out (--no-inhibit/PDCA_NO_INHIBIT), or no binary is present. Linux →systemd-inhibit --what=idle:sleep --why="pdca flow"; macOS →caffeinate -s._inhibit_suspend_and_reexec()—os.execvpes under the wrapper withPDCA_FLOW_INHIBITED=1; warns once on stderr only when the reason is "no inhibitor found".flowdispatch, only on the real CLI entry (argv is None) so a programmaticmain([...])call (tests/embedding) is never replaced viasys.argv.--no-inhibitflag on theflowparser.Advisory by design: inhibits only
idle:sleep, nevershutdown/handle-*, so an operator can still power off. Nomake flowtarget exists, so no Makefile change is needed.Verification
tests/test_flow_inhibit.py(7 new tests) — wraps with systemd-inhibit/caffeinate; returns None when already-inhibited / opted-out (env + flag) / no-binary; prefers systemd-inhibit.make check→ 487 tests pass.python -m pdca_harness.cli flow --rehearse, with a recorder shadowingsystemd-inhibit):systemd-inhibit --what=idle:sleep --why=pdca flow, child hasPDCA_FLOW_INHIBITED=1and does not re-fire (double-wrap guard holds);--no-inhibit→ 0 fires; 3.PDCA_NO_INHIBIT=1→ 0 fires; 4. empty PATH → the one-line keep-awake warning, still runs.🤖 Generated with Claude Code
Review round 1 (Codex) — addressed in 5dbb5fb
python -m pdca_harness.cli flowre-exec was broken (argv[0] = non-executablecli.py) → new_reexec_commandrebuilds module/script invocations assys.executable -m pdca_harness.cli …; console scripts forwarded as-is. Verified live + 2 new tests.caffeinate -sis AC-only → usecaffeinate -i -sso the idle assertion holds on battery.