fix(cron): multiplex ticks isolate per profile, fire the bound port, deliver satellites fail-closed (#74878 #100489 #101113 #86519, salvage #70747 #84755 #96944) - #101245
Merged
Conversation
૮ >ﻌ< ა ci reviewran on 803c683 — docs(profiles): cron delivery for routed profiles rides the
|
|
Thanks for the credit on #74952 — glad the per-profile ticker isolation landed. |
teknium1
force-pushed
the
salvage/mux-cron
branch
4 times, most recently
from
September 2, 2026 13:08
69b633f to
74e9597
Compare
Resolve notepad and suggestion paths at transaction time so multiplexed profile ticks cannot write into the import-time home. Preserve explicit test overrides and cover writes after a profile context switch. Co-authored-by: 이민재 <19909783+honor2030@users.noreply.github.com>
One profile's broken cron store no longer takes the whole multiplex ticker down with it: - startup recovery loop: a per-profile exception (e.g. an unreadable executions.db raising sqlite3.DatabaseError) was uncaught and killed the ticker thread before its first tick — no profile ever fired. - tick loop: only CronTickYielded was caught per profile; any other exception escaped to the cycle-wide handler, skipping every remaining profile that cycle and marking all of them failed. Both loops now catch per profile, record the failure into THAT profile's ticker_last_error (`hermes cron status`), and keep ticking the siblings. The existing CronTickYielded/_profile_errors semantics and the #87644 EMFILE reclaim/backoff are preserved (backoff is applied once per cycle from the worst per-profile failure). Salvaged from PR #70747 (@Cyber-Yichen); the recovery test's real sqlite3.OperationalError shape is from PR #74888 (@OYLFLMH). Same class also reported in PR #74952 (@webtecnica). Co-authored-by: OYLFLMH <95945448+OYLFLMH@users.noreply.github.com> Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>
…stener port Under gateway.multiplex_profiles only the DEFAULT profile's api_server is bound; secondaries share it via /p/<profile>/ mirrors. _gateway_fire_endpoint read the port from the TARGET profile's config.yaml/.env and then prefixed the mirror path, so a secondary with its own API_SERVER_PORT produced a URL nothing listens on (connection refused on every Chronos fire). Multiplex is now detected first (config.yaml + the GATEWAY_MULTIPLEX_PROFILES override via gateway.config._env_multiplex_profiles_override — same semantics as the gateway loader), and in that mode the port is resolved from the default root's config/.env with the fallback logged. Per-profile gateway topology is unchanged. Salvaged from PR #84755 (@bergusdz), with env-override parity restored and the silent except replaced by a debug log.
…p ticker stands down for profiles with their own gateway (#100489) Two mechanisms let the desktop multiplex ticker deliver a secondary profile's cron output through the default profile's identity: 1. _deliver_result's `asyncio.run` ThreadPoolExecutor fallback (taken when the caller already has a running loop — the desktop dashboard shape) ran the standalone sender on a fresh thread with NO profile ContextVars: the home override and secret scope were gone, so the sender resolved the process default's home/token (or, fail-closed under multiplex, raised UnscopedSecretError). Wrap the submit in copy_context().run like the session-db (:6562), heartbeat (:4650) and parallel-pool (:8314) workers. 2. _start_desktop_cron_ticker ticked EVERY local profile, including ones whose own gateway (with live adapters) is running; winning the tick-lock race meant the adapter-less desktop ticker delivered standalone. The multiplex loop gains an optional per-cycle `profile_gate(name, home)`; the desktop wires it to `_check_gateway_running(home)` so such profiles are neither ticked nor heartbeated by the dashboard while their gateway is alive (re-evaluated every cycle, no restart needed). Fixes #100489
…he primary adapter for exact profile_routes targets (#101113) Under gateway.multiplex_profiles a shared-token satellite profile (routed via gateway.profile_routes, no bot credential of its own) got an empty adapter map from the multiplex ticker, so _deliver_result fell through to the standalone sender under the satellite's secret scope and failed with "DISCORD_BOT_TOKEN is not set" — even though the primary adapter owns the exact routed channel and had delivered the same target before. Preflight already rescued this topology (#97476); the delivery half did not. - cron/scheduler.py: factor the preflight's primary-config route loader into `_primary_profile_routes_for_current_home()` (one owner for both halves, so route semantics cannot drift) and add `SharedRouteAdapters`, a read-only view over the primary adapter map that resolves an adapter for a (platform, target) ONLY when an enabled primary route with a chat_id/thread_id maps that exact target to the current profile — using the same `ProfileRoute.matches` predicate as inbound routing. `_deliver_result` resolves the transport per target from it; everything else (unmatched chat, disabled route, route for another profile, no primary adapter, guild-only route) is a miss and never uses the primary bot. Execution stays scoped to the satellite; no credential is copied. - cron/scheduler_provider.py: a secondary with no adapter map of its own gets the SharedRouteAdapters view instead of `{}`. This is NOT a default fallback: with no matching route the view is falsy and delivers nothing. Fixes #101113
…t only for exact routed targets (#101113)
teknium1
force-pushed
the
salvage/mux-cron
branch
from
September 2, 2026 13:19
74e9597 to
803c683
Compare
This was referenced Sep 2, 2026
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.
Summary
Five multiplex cron bugs fixed in one branch: one profile's broken store killed every profile's ticker; dashboard fires targeted an unbound port; the desktop ticker delivered secondaries with the default identity; credentialless
profile_routessatellites couldn't deliver at all; notepad/suggestions paths were frozen at import. Root causes: missing per-profile except in the ticker loops, target-profile port lookup under a shared listener, an unscopedThreadPoolExecutor+ unconditional desktop ticking, an empty adapter map with no route-exact transport resolver, and import-timeget_hermes_home().Changes
except BaseExceptionin recovery + tick loops (CronTickYielded/_profile_errors + gateway: cron scheduler permanently stalls after EMFILE while heartbeat keeps reporting healthy #87644 backoff preserved); optionalprofile_gate;SharedRouteAdaptersview for map-less secondaries.copy_context()on the asyncio.run fallback pool;_primary_profile_routes_for_current_home()shared by preflight + delivery;SharedRouteAdapters(route-exact, fail-closed) used per target.Validation
:8701/p/worker_alpha/…→ connection refusedUnscopedSecretError, default homeCredits
@Cyber-Yichen (#70747, authored), @OYLFLMH (#74888), @webtecnica (#74952), @bergusdz (#84755, authored), @wanliqin + @honor2030 (#96944, cherry-picked), @tachyon-r (#74017, first on suggestions half), @TheBlueHouse75 (#93043, independent identification), reporters @minkiboo (#101113), @herovivian-collab (#100489), @wanliqin (#86519).
Fixes #100489
Fixes #101113
Fixes #86519
Fixes #74878
Infographic