Conversation
…ofiles NousResearch#64674 fixed the DEFAULT profile connecting a token platform whose bot credential lived only in a secondary profile's .env, and wired the reconnect watcher to drop such queued configs. The mirror case was left open: a SECONDARY profile can have discord/telegram enabled — the plugin registry auto-enables them for any profile listing those plugins — while the credential lives only in the default profile's .env. That is the normal shape for a profile used purely as a gateway.profile_routes target: it supplies the model/tools/memory/persona, and inbound arrives on the default profile's single connection. _start_one_profile_adapters built an adapter anyway, so every gateway start logged a bare "No bot token configured" followed by "x <platform> failed to connect (profile: <name>)" for a connection that could never succeed. Apply the existing _platform_has_bot_credential guard in that loop too, so all three call sites agree. Reusing the helper also keeps the established semantics: platforms outside PLATFORM_TOKEN_ENV_NAMES are never skipped (Signal session paths, port-binding HTTP adapters), and api_key counts as a credential for adapters that take it as primary. The new suite drives the real _start_one_profile_adapters rather than re-implementing its loop, with a stub complete enough that a regression fails on the "no adapter may be built" assertion instead of dying early on a missing attribute. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ial helper The guard added in the previous commit reused _platform_has_bot_credential. That helper is shared with two call sites that act on the DEFAULT profile's behalf — the multiplex startup skip and the reconnect retry queue — and NousResearch#68746 proposes giving it an os.environ fallback, plus an in-place backfill of the recovered token onto platform_config. Both are correct for those call sites: there, os.environ holds the default profile's own secrets. Both are wrong here. This loop runs outside _profile_runtime_scope, which deliberately does not mutate os.environ, so a token visible in the process env belongs to the DEFAULT profile. Accepting it would build an adapter for a routes-only profile using the default profile's bot identity — two gateway sessions on one token, inbound landing on whichever won the race — and the backfill would leave that borrowed token on a config object we keep in _profile_adapters. That is a quieter, worse version of the bug this branch exists to fix: the old failure at least announced itself as "✗ <platform> failed to connect". The previous commit encoded that contract as a comment asking a future editor of the shared helper to preserve it. Split out _profile_config_has_bot_credential instead, so this call site cannot be broken from a distance no matter how the shared helper evolves. The two keep identical semantics today — platforms outside PLATFORM_TOKEN_ENV_NAMES are never skipped, api_key counts as a credential — so this commit is behavior-neutral on its own. The ~8 duplicated lines are deliberate, against "extend, don't duplicate". The zero-duplication alternative is to reimplement the shared helper as a delegation to this primitive, which conflicts with NousResearch#68746 in exactly the hunk it edits and forces the two PRs to land together. Keeping them textually independent is worth the duplication; the docstring records why they must not be re-merged. test_default_profile_token_in_process_env_is_not_borrowed drives the real _start_one_profile_adapters with the platform's token env var set, and asserts both that no adapter is built and that platform_config.token is left alone. Verified it fails if the call site is pointed back at the shared helper with NousResearch#68746 applied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4c563e4 to
01eebe7
Compare
|
Thanks for the focused multiplexing fix. The premise still holds on current main: The proposed config-only predicate fits the existing isolation contract: GitHub currently reports the PR as mergeable, despite it being behind current main. Automated hermes-sweeper review. |
Resolves one conflict in gateway/run.py. Git aligned this branch's secondary-profile guard against the NousResearch#64674 primary guard in the DEFAULT profile's startup loop — the two loops are near-identical in shape, so the textual match landed in the wrong function. Taking either side verbatim would have been wrong: the primary loop has no profile_name/profile_home in scope. Resolution keeps main's primary guard where it belongs and re-places this branch's guard in _start_one_profile_adapters, after the Platform.RELAY skip, as originally authored. _profile_config_has_bot_credential merged cleanly and is unchanged. Verified _platform_has_bot_credential on current main is still config-only — NousResearch#68746 has not landed, so the contract this branch protects is intact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…figured profiles (#84079) Secondary profile startup and reconnect now call the existing `_platform_has_bot_credential` gate (the same one the primary loop and primary reconnect use since #64674), so an enabled-in-YAML platform whose credential is absent from that profile's secret scope is skipped instead of built with an empty token and fanned out. Independently reported and fixed in #72313 (@manny3), which added a duplicate helper; the shared main helper is used here instead. Co-authored-by: manny3 <16465310+manny3@users.noreply.github.com>
…figured profiles (#84079) Secondary profile startup and reconnect now call the existing `_platform_has_bot_credential` gate (the same one the primary loop and primary reconnect use since #64674), so an enabled-in-YAML platform whose credential is absent from that profile's secret scope is skipped instead of built with an empty token and fanned out. Independently reported and fixed in #72313 (@manny3), which added a duplicate helper; the shared main helper is used here instead. Co-authored-by: manny3 <16465310+manny3@users.noreply.github.com>
…figured profiles (#84079) Secondary profile startup and reconnect now call the existing `_platform_has_bot_credential` gate (the same one the primary loop and primary reconnect use since #64674), so an enabled-in-YAML platform whose credential is absent from that profile's secret scope is skipped instead of built with an empty token and fanned out. Independently reported and fixed in #72313 (@manny3), which added a duplicate helper; the shared main helper is used here instead. Co-authored-by: manny3 <16465310+manny3@users.noreply.github.com>
|
Thanks for this PR. Merged via #101244 (0fd9218) on current main — routed multiplex profiles stop leaking .env into os.environ or borrowing default creds. #101244 won as the consolidated fix because it covers the whole multiplex-profile bug class in one change (with tests) rather than the single symptom addressed here; this PR is superseded by it. If anything from your original change is still missing on main >= 0fd9218, please open a fresh PR/issue against main and tag it. Thanks again. |
…figured profiles (NousResearch#84079) Secondary profile startup and reconnect now call the existing `_platform_has_bot_credential` gate (the same one the primary loop and primary reconnect use since NousResearch#64674), so an enabled-in-YAML platform whose credential is absent from that profile's secret scope is skipped instead of built with an empty token and fanned out. Independently reported and fixed in NousResearch#72313 (@manny3), which added a duplicate helper; the shared main helper is used here instead. Co-authored-by: manny3 <16465310+manny3@users.noreply.github.com>
What does this PR do?
Fixes the mirror case of #64674, which was left open.
#64674 / #65525 fixed the default profile connecting a token platform whose bot credential lived only in a secondary profile's
.env, and taught the reconnect watcher to drop such queued configs. The opposite direction still fails: a secondary profile can havediscord/telegramenabled — the plugin registry auto-enables them for any profile that lists those plugins — while the credential lives only in the default profile's.env.That is the normal shape for a profile used purely as a
gateway.profile_routestarget. Pergateway/profile_routing.py, such a profile supplies the model/tools/memory/persona, and inbound arrives on the default profile's single connection — it is not a second bot and has no reason to hold a token. But_start_one_profile_adaptersbuilds an adapter for it anyway, so every gateway start logs:for connections that can never succeed.
_platform_has_bot_credential()already exists for exactly this decision and is applied at the two other sites that need it (run.py:7758primary startup,run.py:8740reconnect watcher). This adds the missing third call site so all three agree.Reusing the helper rather than writing a fresh check keeps its established semantics: platforms outside
PLATFORM_TOKEN_ENV_NAMESare never skipped (Signal session paths, port-binding HTTP adapters), andapi_keycounts as a credential for adapters that take it as primary.Interaction with #68746 — please read before merging
#68746 is open and modifies
_platform_has_bot_credential()itself, adding a live raw-os.environfallback for the reconnect path. That fallback is correct there but wrong at this new call site: the process env holds the default profile's tokens, and_profile_runtime_scopedeliberately does not mutateos.environ(see its docstring). So if #68746 lands as written, this guard would see the default profile'sDISCORD_BOT_TOKEN, decide the routes-only profile "has" a credential, and start connecting the default profile's bot on every secondary profile again — silently reintroducing this bug.Whichever of the two merges second should make the env re-check resolve through the profile's secret scope (
get_secret) rather than rawos.environ, or skip the fallback for the secondary-profile call. There is a code comment at the call site flagging this so it is not lost. Happy to rebase onto #68746 and do that reconciliation here if you'd prefer it in one PR.Related Issue
Mirror case of #64674 (fixed for the primary path by #65525). No separate issue filed — say the word and I'll open one.
Fixes #
Type of Change
Changes Made
gateway/run.py— in_start_one_profile_adapters(), skip a platform when_platform_has_bot_credential()reports no credential for that profile's config, logging at INFO which profile was skipped and why instead of failing an impossible connection. Placed after the existingPlatform.RELAYskip.tests/gateway/test_secondary_profile_missing_token_skip.py— new suite (4 cases): missing token (bothNoneand"") is skipped; a profile with its own token still connects;api_keyalone counts as a credential; a non-token platform (SIGNAL) is never skipped.The tests drive the real
GatewayRunner._start_one_profile_adaptersrather than re-implementing its loop body, so the guard cannot regress silently. The runner stub is deliberately complete enough that removing the guard fails on the meaningfulcreated == []assertion — my first version died early on an incidentalAttributeErrorinstead, which would have made the suite near-worthless as a regression guard.How to Test
Reproduce (this is the live configuration the bug was found on):
DISCORD_BOT_TOKENin~/.hermes/.env, andgateway.multiplex_profiles: truewith aprofile_routesentry pointing a channel at a secondary profile:profiles/mentor/.envwith no messaging tokens (correct: one bot token cannot be polled twice, and the same-token detector atrun.py:9592already refuses that).mainevery start logsNo bot token configured+✗ discord failed to connect (profile: mentor)for both discord and telegram.Verify the fix — with this branch, the same start instead logs:
Confirmed live on the setup above: zero
failed to connectlines, both default-profile platforms still connect, and routing is unaffected —match_profile_route()still resolves the routed channel tomentorand other channels to the default profile.Automated:
Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass — see note belowDocumentation & Housekeeping
docs/, docstrings) — or N/A — N/A: no user-facing config or API surface changes; the reasoning lives in a code comment at the call sitecli-config.yaml.exampleif I added/changed config keys — or N/A — N/A: no new config keysCONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A — N/AScreenshots / Logs
Before (on
main, every gateway start):After (this branch, same config, nothing else changed):
🤖 Generated with Claude Code