Repository navigation
Codex App heartbeat backoff fallback fails with empty error and repeats NOTIFY #3231
Description
Symptom and impact
On LoopX 0.4.6 (installed release 20260813T192037Z) with the Codex App heartbeat, an active-work scheduler hint asked for a 3-minute cadence. When the decision moved to monitor wait and stateful_backoff.apply_needed=true requested a 30-minute RRULE, the loopx-apply-rrule fallback exited with code 1 and printed only:
loopx should-run failed (1):
No stderr detail was emitted. The host automation therefore stayed at FREQ=MINUTELY;INTERVAL=3, and subsequent quota should-run decisions alternated between user_todo_blocker_push and user_gate_cooldown_wait. Even when heartbeat_recommendation.notify=DONT_NOTIFY, interaction_contract.user_channel.notify=NOTIFY with action_required=true kept firing, so the user received repeated notifications instead of quiet backoff.
Impact: notification spam, wasted heartbeat turns, and scheduler backoff not being applied on the host.
Expected behavior
- The fallback bridge should either use a fresh idempotent turn id for its internal
quota should-runcall, or surface the fullquota should-runfailure payload instead of an empty error. - When
heartbeat_recommendation.notify=DONT_NOTIFY, the user notification channel should stay quiet during the cooldown window even ifuser_channel.notifystill carries the pending items.
Actual behavior
quota should-run --codex-app --turn-instance-id <T>settles a heartbeat receipt.loopx-apply-rrule --turn-instance-id <T>internally runsquota should-runwith the same<T>.- The internal call exits 1 with empty stderr;
codex_app_apply_rrule.pyprintsloopx should-run failed (1):and returns failure. - Host RRULE remains at 3 minutes;
quota scheduler-fail-currentrecords the failed target/observed pair. - Later heartbeat decisions alternate between blocker push and cooldown wait, and repeated NOTIFY messages are delivered despite
heartbeat_recommendation.notify=DONT_NOTIFY.
Minimal public-safe reproduction
Use a public/synthetic goal with an open user gate and a Codex App scheduler hint:
- Run
loopx quota should-run --goal-id <fixture> --agent-id <agent> --codex-app --turn-instance-id T. - In the same turn, run
loopx-apply-rrule --goal-id <fixture> --agent-id <agent> --turn-instance-id T. - Observe exit code 1 and empty stderr on the second command when the should-run receipt for
Tis already settled. - Repeat
quota should-runand observe the notification mode flip betweenuser_todo_blocker_pushanduser_gate_cooldown_wait.
No task text, credentials, private paths, internal URLs, or project identifiers are included.
Environment
- LoopX: 0.4.6, installed release
20260813T192037Z - Host: Codex App heartbeat on macOS
- Fallback bridge:
scripts/codex_app_apply_rrule.py
Suspected surface
scheduler_hint.codex_app.stateful_backoff/loopx-apply-rrulefallback lifecycleuser_gate_notification_cooldownprojection whenheartbeat_recommendation.notify=DONT_NOTIFY- Failure diagnostics from
codex_app_apply_rrule._load_scheduler_hint
Validation already run
- Repeated independent heartbeat turns reproduced the 3-minute host RRULE persistence and the repeated NOTIFY pattern.
quota scheduler-fail-currentrecorded the failed target/observed pair without quota spend.- The exact failure occurred with a settled turn-instance id; fresh turn ids applied RRULE successfully.
Activity