Skip to content

fix(cron): defer desktop ticker to gateway scheduler owner#44049

Open
AIalliAI wants to merge 1 commit into
NousResearch:mainfrom
AIalliAI:fix/desktop-cron-scheduler-ownership
Open

fix(cron): defer desktop ticker to gateway scheduler owner#44049
AIalliAI wants to merge 1 commit into
NousResearch:mainfrom
AIalliAI:fix/desktop-cron-scheduler-ownership

Conversation

@AIalliAI

@AIalliAI AIalliAI commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #43965

Problem

The Desktop dashboard backend (HERMES_DESKTOP=1) runs its own cron ticker, sharing only cron/.tick.lock with the launchd gateway ticker. That lock gives at-most-once execution but not deterministic ownership — whichever process wins the lock runs the job. On macOS, TCC / Full Disk Access depends on process ancestry, so a job that fired under the dashboard ancestry lost access to protected local data that the same job could read when run by the launchd gateway chain.

Observed with a launchd-managed gateway (ai.hermes.gateway-<profile>) and Desktop-spawned dashboard backends alive for the same profile: the dashboard ticker sometimes won cron/.tick.lock and executed an hourly job without the gateway's FDA provenance. After terminating the dashboard backends, the same job succeeded from the gateway ancestry.

Related: #25737 (durable server-side cron runner), #40684 (added the desktop ticker, relying on the tick lock), #43652 (tick-lock release semantics).

Steps to reproduce

  1. macOS, profile with a launchd-managed gateway: hermes --profile <p> gateway run --replace
  2. A cron job for that profile that reads TCC/FDA-protected local data
  3. Open Hermes Desktop so dashboard backends (HERMES_DESKTOP=1) are alive for the same profile
  4. Wait for the job — when the dashboard ticker wins the lock, the job runs without FDA provenance and fails to read protected data

Fix

Use the gateway's per-profile runtime lock (gateway.status.is_gateway_runtime_lock_active, held for the live gateway's lifetime, OS-released on death — never stale) as the scheduler-ownership signal:

  • cron/scheduler.py: new _gateway_scheduler_owner_active() helper (lazy import, fails open if the signal is unavailable) and a keyword-only defer_to_gateway_owner=False parameter on tick(). When set and a gateway owns the scheduler, the tick returns 0 before taking the tick lock or inspecting jobs.
  • hermes_cli/web_server.py: the desktop ticker passes defer_to_gateway_owner=True and the docstring no longer claims the tick lock alone makes it cross-process safe.
  • The gateway's own ticker (gateway/run.py) keeps the default False, so it always runs.

Net effect: with a gateway running, only the gateway executes jobs (correct provenance). With no gateway (desktop-only setup), the dashboard ticker keeps firing jobs exactly as before.

Tests

New tests/cron/test_scheduler_ownership.py (5 tests): deferral when a gateway owner is active (no job inspection, no lock taken), normal execution when no owner, gateway ticker unaffected by the guard, helper delegation to gateway.status, fail-open on error.

Ran via scripts/run_tests.sh on macOS: new file plus tests/cron/test_scheduler.py, tests/cron/test_parallel_pool.py, tests/test_web_server.py, tests/gateway/test_status.py — 207 tests, all passing. Platforms tested: macOS (the bug is macOS-specific; the guard itself is platform-neutral and a no-op when no gateway lock is held).

🤖 Generated with Claude Code

The Desktop dashboard cron ticker shared only cron/.tick.lock with the
launchd gateway ticker, which gives at-most-once execution but not
deterministic execution provenance: whichever ticker won the lock ran
the job. On macOS, TCC / Full Disk Access depends on process ancestry,
so jobs that won under the dashboard backend lost access to protected
local data.

tick() now accepts defer_to_gateway_owner; the desktop ticker passes
True and skips execution while a gateway holds the per-profile runtime
lock (gateway.status.is_gateway_runtime_lock_active). With no gateway
running, desktop-only setups keep firing jobs as before.

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

Copy link
Copy Markdown
Contributor Author

Requesting maintainer review — this is ready to land from my side. Standalone fork CI is pending first-run approval here; the rollup branch in #44061 carrying this session's batch is fully green on upstream CI (all test shards, typecheck, e2e).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Desktop dashboard cron ticker can execute jobs instead of launchd gateway, breaking macOS TCC/FDA provenance

2 participants