Skip to content

feat(wrapper): launch claude via caffeinate to prevent idle sleep - #125

Merged
twistedmelonman merged 1 commit into
mainfrom
claude/caffeinate-launch-5696
Sep 16, 2026
Merged

twistedmelonman merged 1 commit into
mainfrom
claude/caffeinate-launch-5696

Conversation

@twistedmelonman

@twistedmelonman twistedmelonman commented Sep 16, 2026

Copy link
Copy Markdown
Member

What

Launches claude through caffeinate -i so the system does not idle-sleep partway through a long CCCLI session.

caffeinate holds a PreventUserIdleSystemSleep assertion for the life of the utility it launches and releases it on exit — verified no leak across 11 sequential invocations.

Why hardcode /usr/bin/caffeinate

caffeinate runs with GH_TOKEN and OP_SERVICE_ACCOUNT_TOKEN in its environment. Resolving it with command -v means a same-named binary earlier in $PATH receives both, and bypasses the ownership/permission validation this repo applies to the claude binary. SIP guarantees the /usr/bin path and prevents it being replaced.

Behavior notes

caffeinate execs the utility in place rather than supervising it. Verified directly: the child reports the same PID as caffeinate, a trapped SIGTERM reaches it, exit code 7 propagates unchanged, and the TTY survives under a pty. Nothing downstream needs to forward signals or propagate exit codes.

-i holds the assertion on battery as well as AC; -s is AC-only.

Test 4.3 had never run

Found while updating the test for the new exec line. The stale sed pattern was the smaller problem: test 4.3 copied the wrapper to TEST_TMP, but the wrapper resolves WRAPPER_LIB as <own dir>/../lib, so the copy could not source lib/logging.sh. It exited 1 before reaching exec and landed in a branch that prints a skip and returns 0 — passing without asserting anything, since it was written.

The copy now lives beside the real wrapper and is removed by a RETURN trap. Both of its assertions ("Mock claude was executed", "Git identity passed through") pass for the first time. bin/.test-integration-wrapper.* is gitignored so an interrupted run cannot leave an untracked file in bin/.

Because it now runs for real, it sources credentials.sh, which may hit the Keychain when the tokens are not already exported. Both lookups are timeout-bounded and degrade to no-ops, so the suite still passes offline in ~2.5s.

New coverage

Test 4.5 asserts the path is hardcoded, is not PATH-resolved, and that every exec branch goes through caffeinate -i. Both assertions were validated against known-bad inputs: reverting to command -v fails two of them, and dropping caffeinate from one branch fails the branch-count check.

Verification

  • 4 suites pass: 69 + 26 + 24 + 6
  • shellcheck -S info clean across lib/, bin/, tests/ — no disable directives added
  • markdownlint clean
  • Skipped tests/test-gh-token-permissions.sh (performs live mutations; untouched by this change)

https://claude.ai/code/session_019n5uxsRsrT4PDiYAxHUYyu

The system could idle-sleep partway through a long CCCLI session. The
wrapper now execs claude through `caffeinate -i`, which holds a
PreventUserIdleSystemSleep assertion for the life of the utility it
launches and releases it on exit.

caffeinate execs the utility in place, so claude keeps the same PID, TTY,
signal disposition, and exit status it had before. Nothing needs to
forward signals or propagate exit codes.

CAFF_BIN is hardcoded to /usr/bin/caffeinate rather than resolved with
`command -v`: caffeinate inherits GH_TOKEN and OP_SERVICE_ACCOUNT_TOKEN,
so a same-named binary earlier in PATH would receive both and would
bypass the ownership/permission validation applied to the claude binary.
SIP guarantees the path.

Also fixes a pre-existing bug in test 4.3, which has never actually run.
It copied the wrapper to TEST_TMP, but the wrapper resolves WRAPPER_LIB
as "<own dir>/../lib" -- so the copy could not source lib/logging.sh and
exited 1 before reaching exec, landing in a branch that reports a skip
and passes. The copy now lives beside the real wrapper and is removed by
a RETURN trap. Both of its assertions pass for the first time.

Adds test 4.5 to cover the new behavior: caffeinate is hardcoded, is not
PATH-resolved, and every exec branch goes through `caffeinate -i`. Both
assertions were verified to fail against the corresponding regressions.

Closes #121
@twistedmelonman twistedmelonman changed the title probe feat(wrapper): launch claude via caffeinate to prevent idle sleep Sep 16, 2026
@claude

claude Bot commented Sep 16, 2026

Copy link
Copy Markdown

The PR wraps the claude exec with caffeinate -i to prevent idle sleep during long sessions.

No blocking issues found. The hardcoded /usr/bin/caffeinate path is justified — caffeinate inherits GH_TOKEN and OP_SERVICE_ACCOUNT_TOKEN, so PATH-resolving it would allow a shadowing binary to receive those tokens while bypassing the ownership/permission validation applied to the claude binary. Both exec branches are updated consistently. The test drift-detection guard (grep for surviving ${CLAUDE_BIN} exec lines after sed) prevents silent test degradation if the exec pattern changes again.

VERDICT: PASS

@twistedmelonman
twistedmelonman merged commit 7757005 into main Sep 16, 2026
4 checks passed
@twistedmelonman
twistedmelonman deleted the claude/caffeinate-launch-5696 branch September 16, 2026 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant