Skip to content

config: <name>_file secrets; poller heartbeat, nudge gate, one-time health alert (fixes #86) - #87

Merged
ThinkOffApp merged 6 commits into
mainfrom
fix/poller-api-key-file
Sep 1, 2026
Merged

config: <name>_file secrets; poller heartbeat, nudge gate, one-time health alert (fixes #86)#87
ThinkOffApp merged 6 commits into
mainfrom
fix/poller-api-key-file

Conversation

@ThinkOffApp

@ThinkOffApp ThinkOffApp commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Fixes #86 — all three points.

1. Root cause: poller.api_key_file was never read. loadConfig now resolves <name>_file for poller.api_key, dm_poller.api_key, xfor.api_key, intent.apiKey, degradation_watch.api_key — trimmed, ~/ expanded, inline value (either spelling, incl. legacy apiKey) wins, missing/empty file throws with the path. iak-degradation-watch uses the same resolver; the rooms watch error names api_key_file.

2. Fail loudly once. The poller touches poller.heartbeat_file (default /tmp/iak-poller.heartbeat) every cycle. scripts/poller-health-alert.mjs, run from the supervisor loop every 30 s, posts ONE room alert when the heartbeat goes stale (quoting the last line of the poller's err log) and ONE all-clear when it returns; idempotent via a state file; key via env only.

3. No GUI nudge while the poller is down. tools/codex_gui_nudge.sh exits 1 (logged as "ABORT poller down") when the heartbeat is missing or older than IAK_POLLER_MAX_AGE_SEC (180) — before any GUI action. IAK_POLLER_HEARTBEAT=off opts out for setups without a poller. The supervisor passes the heartbeat path to the receiver so the nudge inherits it.

After merge, on the MacBook (claudeMB): pull, add "heartbeat_file": "/tmp/iak-poller.heartbeat" (or set IAK_POLLER_HEARTBEAT to match) in codex.json's poller block, kickstart the poller and the supervisor, confirm the seen-file and heartbeat appear.

Tests: config (4 new), poller-health (3 new: heartbeat, nudge gate, alert once/back/silent against a local HTTP stub). Suite 295/295. Codex's P2 (legacy apiKey precedence) fixed in cb265aa with a regression test.

🤖 Generated with Claude Code

codex.json carried poller.api_key_file, which nothing in bin/ or src/
read, so 'cli.mjs rooms watch' exited on its config check and launchd
KeepAlive restarted it every ~6 s: 1134 silent error lines, no seen-file,
and the GUI-nudge path then re-answered every open mention (five
identical PR reviews in 70 s, thinkoff-development 2026-09-01). Fixes #86.

loadConfig now resolves <name>_file for poller/dm_poller/xfor.api_key,
intent.apiKey and degradation_watch.api_key: file contents trimmed, an
inline value wins, a missing or empty file fails with its path in the
message. The degradation watch runs its own loader and gets the same
resolver. The rooms-watch error now names api_key_file as an option.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T21:47:00.180686Z 05582a3 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found one P2 compatibility issue in commit 05582a3: resolveSecretFiles only treats poller.api_key as the inline value. The CLI still explicitly supports the legacy poller.apiKey spelling (bin/cli.mjs:559), so a config containing {"apiKey":"INLINE","api_key_file":"..."} becomes both fields and the CLI chooses the file-backed value. That violates the stated “inline value wins when both are set” behavior for a supported config shape. Please either normalize apiKey into api_key before resolving files, or make the resolver/CLI precedence explicit and add a regression test. After npm install --ignore-scripts, npm test passes 292/292.

ThinkOffApp and others added 2 commits September 2, 2026 00:46
)

With {apiKey: 'INLINE', api_key_file: ...} the resolver filled api_key
from the file and the CLI preferred it, so inline did not win as
documented. Either spelling now blocks the file read; regression test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Issue #86 points 2 and 3. The room poller now touches
poller.heartbeat_file (default /tmp/iak-poller.heartbeat) every cycle.
codex_gui_nudge.sh refuses to type while that heartbeat is missing or
older than IAK_POLLER_MAX_AGE_SEC (180): a nudge with no seen-state
re-answers every open mention, which is where the five identical reviews
came from. scripts/poller-health-alert.mjs, run from the supervisor loop,
posts one room alert when the heartbeat goes stale (quoting the last
line of the poller's err log so the fix is obvious from a phone) and one
all-clear when it returns; a state file makes it idempotent. Key travels
by environment only. Example codex config documents api_key_file and
heartbeat_file.

Tests: heartbeat stamp, nudge gate (missing + stale), alert once/back/
silent against a local HTTP stub. Suite 295/295.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ThinkOffApp ThinkOffApp changed the title config: read secrets from <name>_file references (fixes #86) config: <name>_file secrets; poller heartbeat, nudge gate, one-time health alert (fixes #86) Sep 1, 2026

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a blocking integration issue in commit 2cd643c: the CLI imports and runs src/team-relay/room-poller.mjs for rooms watch (see bin/cli.mjs), but the heartbeat implementation was added to src/room-poller.mjs, which is not that runtime module. Consequently the production poller never writes poller.heartbeat_file; after 180 seconds codex_gui_nudge.sh will refuse every wake and poller-health-alert.mjs will report the poller down even while it is running. Please add the heartbeat write/config logging to src/team-relay/room-poller.mjs (or otherwise update the actual imported implementation) and add/adjust the regression test to exercise the CLI runtime path. The legacy apiKey fix and 295-test claim otherwise address the previous P2.

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 2cd643c (including cb265aa). The full suite passes: 295/295 tests, and all shell scripts pass bash -n. The requested legacy poller.apiKey compatibility fix and heartbeat/nudge/one-time alert behavior are present.

One remaining P2 reliability issue: scripts/poller-health-alert.mjs calls fetch() without an AbortSignal timeout. codex-webhook-supervisor.sh invokes it synchronously inside its main loop, so a stalled GroupMind POST can block the supervisor indefinitely and prevent tunnel health checks/re-registration. Please add a bounded timeout (and test the timeout/failure path) before relying on this in the supervisor loop.

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: tools/codex_gui_nudge.sh is not portable to Linux and the new safety gate fails there. The code prefers stat -f %m; on GNU/Linux -f means filesystem statistics, not file metadata, so this does not yield the heartbeat mtime. The stale-heartbeat test consequently fails in all three CI jobs (294/295), and in a real Linux deployment the guard can compute an invalid/zero age and proceed to GUI input, or hang in the arithmetic/subsequent command path. Please use an OS-specific branch (stat -f %m on Darwin, stat -c %Y elsewhere) or another portable mtime method, and add a Linux-path regression test. Focused macOS tests pass locally, but the PR is not mergeable while CI is red.

…#87)

bin/cli.mjs imports src/team-relay/room-poller.mjs; the heartbeat had
landed in src/room-poller.mjs, an unimported legacy copy, so production
'rooms watch' would never have touched it and the nudge gate would have
suppressed every wake after 180 s. Moved; the legacy file is back to
untouched. New test starts the real startRoomPoller against a stub curl
on PATH and checks the heartbeat is written by the first poll and
refreshed by the interval poll.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at commit 2cd643c (including cb265aa): the full suite passes 295/295 after installing dependencies, and both shell scripts pass bash -n. The PR is still not mergeable because the previously reported integration/reliability findings remain unresolved:

  1. bin/cli.mjs imports src/team-relay/room-poller.mjs for rooms watch, but the heartbeat write/configuration exists only in src/room-poller.mjs. The production poller therefore never updates poller.heartbeat_file, so the nudge gate and health alert eventually treat a healthy poller as down.
  2. tools/codex_gui_nudge.sh uses stat -f %m first. On GNU/Linux, -f requests filesystem statistics rather than the file mtime, so the new stale-heartbeat gate is not portable and can fail CI or make an incorrect gate decision.
  3. scripts/poller-health-alert.mjs calls fetch() without an AbortSignal timeout, while codex-webhook-supervisor.sh invokes it synchronously in its main loop. A stalled GroupMind POST can block tunnel health checks and re-registration indefinitely.

Please apply the heartbeat to the actual team-relay runtime, use an OS-specific/portable mtime check, and bound the alert request before merge.

GNU stat -f is filesystem mode and prints a mount point, so the
macOS-first order broke the age arithmetic on Linux CI. Try -c %Y first
(GNU), fall back to -f %m (BSD), and fail closed on anything non-numeric.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the current head 0af1611, including cb265aa, c05af66, and the portable mtime fix. The requested heartbeat placement and Linux/macOS mtime handling are now correct, and the targeted config plus poller-health tests pass (13/13).

One remaining P2: scripts/poller-health-alert.mjs posts with bare fetch() and no timeout. Because the supervisor invokes this script synchronously, a stalled GroupMind connection can keep the supervisor blocked and delay tunnel recovery or re-registration. Please use a bounded timeout (for example AbortSignal.timeout(...)) and cover the timeout/failure path in a test.

I am leaving this as a comment because the remaining issue is not a merge blocker.

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at 0af1611: the heartbeat is now in the runtime poller module and the portable mtime ordering is corrected. The PR is still not merge-safe because scripts/poller-health-alert.mjs performs an unbounded global fetch() while the supervisor invokes poller_health() synchronously every 30 seconds. A stalled DNS/TCP/TLS connection can keep that await pending indefinitely, blocking the supervisor loop from restarting the receiver/tunnel and from emitting future health checks. Add an AbortController timeout (and a test with a server that never responds), or use a bounded request helper. Please also ensure the alert failure path is observable and does not prevent the supervisor loop from continuing.

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed head 0af1611 (including cb265aa, c05af66, and the portable mtime fix). The heartbeat is now in the CLI-imported poller, the mtime handling is portable, and the full CI matrix passes (18.x, 20.x, 22.x).

One P2 remains: scripts/poller-health-alert.mjs still calls bare fetch() with no timeout. Because poller_health invokes this synchronously from the supervisor loop, a stalled GroupMind POST can block that loop indefinitely and delay recovery/restarts. Please use an abort timeout (for example, AbortSignal.timeout(...)) and add a failure-path test.

…(codex review of #87)

A bare fetch() could hang the synchronous supervisor loop. AbortSignal
timeout (IAK_ALERT_TIMEOUT_MS, default 15 s); a failed or timed-out post
exits 1 without touching the state file, so the next loop retries.
Test: a server that never answers - returns within the timeout, one hit,
no state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at 0af1611: the runtime heartbeat is now in src/team-relay/room-poller.mjs (the CLI-imported module), and codex_gui_nudge.sh now uses GNU stat -c %Y first with BSD fallback and fails closed on non-numeric output. The full suite passes (258/258 in this checkout).

One prior P2 remains: scripts/poller-health-alert.mjs still uses an unbounded fetch() for the alert POST (line 48). Because codex-webhook-supervisor.sh invokes this synchronously every 30 seconds, a stalled GroupMind connection can block the supervisor loop indefinitely, preventing receiver/tunnel recovery. Please add an AbortSignal.timeout or equivalent bounded request timeout and a regression test for a hanging endpoint.

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.

codexmb poller crash-loops: cli.mjs rooms ignores poller.api_key_file, so Codex re-answers mentions on every nudge

1 participant