Skip to content

fix(honcho): harden self-hosted setup paths#33954

Closed
erosika wants to merge 15 commits into
NousResearch:mainfrom
erosika:eri/honcho-selfhost-compat-cleanup
Closed

fix(honcho): harden self-hosted setup paths#33954
erosika wants to merge 15 commits into
NousResearch:mainfrom
erosika:eri/honcho-selfhost-compat-cleanup

Conversation

@erosika

@erosika erosika commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Self-hosted Honcho setup had four sharp edges: local URLs with /v3 could be double-prefixed by the SDK, authenticated local servers had no setup prompt for a JWT/bearer token, profile-derived host keys could fall back to dot-containing workspace IDs that Honcho rejects, and memory-provider config files containing API keys could be written with world-readable permissions depending on umask.

This PR keeps existing behavior but makes those paths safer:

  • strip a trailing /vN version segment from any configured baseUrl before SDK initialization, avoiding /v3/v3/... routes. The SDK's route builders always prepend their own version prefix, so this applies to every host — loopback, LAN IP, Tailscale, custom domain, and cloud alike (a pasted /v3 double-prefixes regardless of host). Auth-skipping (the local API-key placeholder) stays loopback-only.
  • add an optional local JWT/bearer token prompt in hermes honcho setup, stored under hosts.<host>.apiKey
  • derive new profile host keys with underscores, while still reading legacy hermes.<profile> host blocks
  • write memory-provider config files containing API keys atomically with 0600 permissions, avoiding the chmod-after-write TOCTOU window. This lands across honcho/hindsight/mem0/supermemory because it's a single shared-helper change (utils.atomic_json_write gains a mode= arg) rather than per-provider behavior changes.
  • skip Honcho honcho.json parsing in gateway cache-busting when Honcho is not the active memory provider; memoize by honcho.json mtime when it is active
  • bust the gateway agent cache on memory.provider change. Adopted from fix(gateway): skip inactive honcho cache-bust reads #33535 (BROCCOLO1D): the provider value gates whether honcho.json is parsed, but wasn't itself a cache-busting key, so a mid-gateway provider switch could miss a rebuild. Co-authored credit on the commit.
  • add a hermes memory setup <provider> one-liner so a fresh install can configure a named provider (e.g. hermes memory setup honcho) without the picker. The per-provider hermes <provider> subcommand is only registered once that provider is active, so on a fresh install the documented hermes honcho setup doesn't exist yet and argparse rejects it with invalid choice: 'honcho'. hermes memory setup is always available regardless of active provider, so it now takes an optional positional that routes to the same setup wizard. Docs updated to lead with the one-liner.

Closes #20688.
Closes #29885.
Closes #26459.
Closes #30246.
Closes #33382.
Closes #32244.

Supersedes #20720, #30180, #32244, and the self-hosted/safe-host-key portion of #26478.
Supersedes #33535 (BROCCOLO1D) — adopted its memory.provider cache-busting key; the rest converged with this PR's gateway/run.py changes.

Kept separate

Test plan

  • scripts/run_tests.sh tests/honcho_plugin/test_client.py tests/honcho_plugin/test_cli.py tests/gateway/test_agent_cache.py tests/hermes_cli/test_profiles.py tests/test_honcho_client_config.py tests/plugins/memory/test_hindsight_provider.py tests/plugins/memory/test_mem0_v2.py tests/plugins/memory/test_supermemory_provider.py

Result: 449 passed, 0 failed (Honcho suites). The hermes memory setup <provider> routing adds tests/hermes_cli/test_memory_setup_provider_arg.py (4 tests).

erosika and others added 6 commits May 27, 2026 17:38
…ed instances (NousResearch#20688)

The Honcho SDK's route builders (e.g. routes.workspaces()) return paths that
already include the API version prefix (e.g. "/v3/workspaces"). When a user
configures a self-hosted instance with base_url = "http://localhost:38000/v3",
the SDK concatenates them into "/v3/v3/workspaces", causing 404 errors on every
peer and session initialization call.

Strip any trailing version path segment (/vN) from base_url before passing it
to the Honcho constructor for local instances (localhost, 127.0.0.1, ::1).
Cloud base_urls are not local and remain unchanged.

Adds four regression tests in tests/honcho_plugin/test_client.py covering:
- /v3 suffix stripped for localhost URL
- no-version URL left unchanged
- cloud URL not modified
- /v3/ with trailing slash also cleaned

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins comp/gateway Gateway runner, session dispatch, delivery labels May 28, 2026
AhmetArif0 and others added 2 commits May 28, 2026 11:59
…ontaining API keys

Four memory plugin save_config() methods wrote their JSON config files via
write_text(), which obeys the process umask (typically 0o022), leaving
them at 0644 — world-readable on shared hosts:

  ~/.hermes/honcho.json       — Honcho API key
  ~/.hermes/mem0.json         — Mem0 Platform API key (required)
  ~/.hermes/hindsight/config.json — Hindsight Cloud API key
  ~/.hermes/supermemory.json  — Supermemory API key (required)

All four schemas declare api_key with "secret": True. A local user on a
multi-user host could read these files and recover live API keys.

Apply os.chmod(config_path, 0o600) immediately after write_text() at
each of the four save_config() call sites, matching the pattern used by
the recent security cluster (79fc92e for .env, 3bace07 for
webhook_subscriptions.json, 782681f for Google Chat OAuth).

honcho/__init__.py also adds a local `import os` inside save_config()
because that module does not import os at the top level.

Adds one regression test per plugin asserting that the resulting file
mode is 0o600 under a permissive umask (skipped on Windows).
@erosika

erosika commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the TOCTOU concern from #32244: memory provider config writes now use atomic JSON writes with the temp file created/replaced at 0600, instead of write_text() followed by chmod. Targeted tests still pass: 436 passed.

@erosika

erosika commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

Fixed the cache-busting test failures from CI: those assertions now run with memory.provider=honcho, matching the new gate that skips Honcho config reads for inactive providers. Expanded targeted test pass: 479 passed.

erosika added 3 commits May 28, 2026 15:30
The /v3 double-prefix fix was gated on loopback (localhost/127.0.0.1/::1),
so a self-hosted Honcho reached by any other address — LAN IP, Tailscale,
custom domain, Fly.io — kept its trailing /vN and re-hit the /v3/v3 -> 404
the prior commit set out to fix.

Version stripping is a pure routing concern (the SDK always prepends its
own /vN), so it now applies to any base_url regardless of host. Auth-skip
behavior stays loopback-only, since substituting the 'local' placeholder
only makes sense for a no-auth localhost server.

Tests: add LAN/Tailscale/custom-domain regression cases; split the cloud
test so a cloud base_url with /v3 is stripped (it double-prefixes too) and
one without a version segment is left unchanged.
…ompat-cleanup

# Conflicts:
#	tests/plugins/memory/test_mem0_v2.py
#	tests/test_honcho_client_config.py
The per-provider 'hermes <provider>' subcommand (e.g. 'hermes honcho setup')
is only registered once that provider is active, so on a fresh install the
documented setup command doesn't exist yet — argparse rejects it with
'invalid choice: honcho'.

'hermes memory setup' is always available regardless of active provider, so
give it an optional positional that skips the picker and configures a named
provider directly. 'hermes memory setup honcho' now works cold.

Docs (honcho README, website honcho.md + memory-providers.md, honcho skill)
updated to lead with the one-liner and note 'hermes honcho setup' as the
post-activation alias.
@erosika erosika marked this pull request as ready for review May 29, 2026 18:55
Adopted from NousResearch#33535 (BROCCOLO1D): the per-message Honcho cache-busting
gate reads memory.provider to decide whether to parse honcho.json, but the
provider value itself was not a busting key. A user switching memory.provider
mid-gateway (e.g. honcho -> mem0) would not necessarily rebuild the cached
agent on that change alone. Add ("memory", "provider") to the busting key
set and cover the provider-switch signature change with a test.

Co-authored-by: BROCCOLO1D <279959838+BROCCOLO1D@users.noreply.github.com>
erosika added 2 commits May 29, 2026 16:30
main's test_packaging_metadata.py imports setuptools at module top via
`from setuptools import find_packages`. setuptools is a build-system
requirement, not a test-venv dependency, and uv-managed CI interpreters
don't seed it — so collection errors with ModuleNotFoundError and fails
the whole test shard on every PR built on current main.

Use pytest.importorskip so the module skips cleanly when setuptools is
unavailable and runs normally when it is. Unrelated to the Honcho changes
in this PR; included to unblock CI.
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Merged via #35170 (commit 827ce60) — salvaged onto current main with your authorship preserved (Erosika <eri@plasticlabs.ai>). The only thing dropped was the tests/test_packaging_metadata.py importorskip change, which is now redundant with #34851 (setuptools declared in the dev extra). BROCCOLO1D credited as co-author for the adopted memory.provider cache-busting key. Thanks for the thorough self-hosted hardening work! All six linked issues close with the merge.

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