Repository navigation
Conversation
…d state.db parent Deriving a profile's home from Path(authority.db.db_path).resolve().parent breaks when state.db is a symlink outside the profile home: the runtime scope, config reads and owner keys then point at the link target's directory (no .env, no skills), so multiplex cron agent runs die with ProviderNotConfiguredError and 'skill not found' warnings. Add session_authorities.authority_home(): the store path's own parent, unresolved. All former resolve().parent sites use it; the media sweep guard compares stores with samefile. Closes #145075
_build_profile_authority() compared the resolved database parent with the profile home, rejecting a state.db symlinked onto a separate dataset (ZFS/NFS volume): the gateway aborted with 'session authority database does not belong to the reserved profile' even though the opened database was exactly the profile's own store. Decide ownership by file identity instead: the opened database must be the same file as the profile's state.db (os.path.samefile), wherever it physically lives. A handle for a genuinely different file is still rejected, as the new regression tests pin down.
…groups owner check authority_home() returned the unresolved store parent, so every caller still had to remember to resolve() before comparing or keying it (bind_owner did; config reads and the stored credential reference did not). Resolving the directory keeps the canonical home run_runtime serves and stamps into existing credential references for every non-symlinked store, while still never following the state.db link itself. dispatch_group_control's owner check had the same .resolve().parent bug and refused every Bots/groups call on a symlinked store with profile_mismatch.
…ed store (#145075) Sibling-site coverage from #145834 (authority_for_home, profile_config, orphan_close_grace, config_reference, provider resolution, cron owner key), plus the group-controls owner check. Co-authored-by: Prince Jain <[email protected]>
૮ >ﻌ< ა ci reviewran on 8357372 — test(gateway): release the registry handles the symlinked-st
|
| Job | Step | Logs | Restored | Δ vs main |
|---|---|---|---|---|
| Catalog tests / Catalog tests | Set up locked Python and test dependencies / Restore verified PM tools without saving | view step | 80.77 MiB | +0 B |
| Catalog tests / Catalog tests | Set up locked Python and test dependencies / Restore uv dependency downloads and builds | view step | 78.48 MiB | +0 B |
| Repository guards / Repository guards | Run ./.github/actions/setup-pm / Restore verified PM tools without saving | view step | 153.06 MiB | +0 B |
| Repository guards / Repository guards | Run ./.github/actions/setup-pm / Restore uv dependency downloads and builds | view step | 78.48 MiB | +0 B |
| Repository guards / Repository guards | Run ./.github/actions/setup-pm / Restore node dependency downloads | view step | 180.79 MiB | +0 B |
| Python tests / Run tests (3/3) | Set up locked Python and test dependencies / Restore verified PM tools without saving | view step | 152.33 MiB | +0 B |
| Python tests / Run tests (3/3) | Set up locked Python and test dependencies / Restore uv dependency downloads and builds | view step | 78.48 MiB | +0 B |
| Python tests / Run tests (3/3) | Run tests | view step | 0.09 MiB | +0 B |
| Python lints / Python lints | Run ./.github/actions/setup-pm / Restore verified PM tools without saving | view step | 80.77 MiB | +0 B |
| Python tests / Run tests (1/3) | Set up locked Python and test dependencies / Restore verified PM tools without saving | view step | 152.33 MiB | +0 B |
| Python tests / Run tests (1/3) | Set up locked Python and test dependencies / Restore uv dependency downloads and builds | view step | 78.48 MiB | +0 B |
| Python tests / Run tests (1/3) | Run tests | view step | 0.09 MiB | +173 B |
| Python tests / Run tests (2/3) | Set up locked Python and test dependencies / Restore verified PM tools without saving | view step | 152.33 MiB | +0 B |
| Python tests / Run tests (2/3) | Set up locked Python and test dependencies / Restore uv dependency downloads and builds | view step | 78.48 MiB | +0 B |
| Python tests / Run tests (2/3) | Run tests | view step | 0.09 MiB | +61 B |
| OS-specific tests / macOS-only tests (1/2) | Set up locked Python and test dependencies / Restore verified PM tools without saving | view step | 105.12 MiB | +0 B |
| OS-specific tests / macOS-only tests (1/2) | Set up locked Python and test dependencies / Restore uv dependency downloads and builds | view step | 165.31 MiB | +0 B |
| OS-specific tests / Windows-only tests | Set up locked Python and test dependencies / Restore verified PM tools without saving | view step | 231.08 MiB | +0 B |
| OS-specific tests / Windows-only tests | Set up locked Python and test dependencies / Restore uv dependency downloads and builds | view step | 394.47 MiB | +0 B |
| OS-specific tests / macOS-only tests (2/2) | Set up locked Python and test dependencies / Restore verified PM tools without saving | view step | 105.12 MiB | +0 B |
| OS-specific tests / macOS-only tests (2/2) | Set up locked Python and test dependencies / Restore uv dependency downloads and builds | view step | 165.31 MiB | +0 B |
| OS-specific tests / Windows-only tests (arm64, 2/2) | Set up native ARM64 build dependencies / Cache ARM64 OpenSSL development files | view step | 26.23 MiB | +0 B |
| OS-specific tests / Windows-only tests (arm64, 2/2) | Set up locked Python and test dependencies / Restore verified PM tools without saving | view step | 218.46 MiB | +0 B |
| OS-specific tests / Windows-only tests (arm64, 2/2) | Set up locked Python and test dependencies / Restore uv dependency downloads and builds | view step | 853.18 MiB | +0 B |
| OS-specific tests / Windows-only tests (arm64, 1/2) | Set up native ARM64 build dependencies / Cache ARM64 OpenSSL development files | view step | 26.23 MiB | +0 B |
| OS-specific tests / Windows-only tests (arm64, 1/2) | Set up locked Python and test dependencies / Restore verified PM tools without saving | view step | 218.46 MiB | +0 B |
| OS-specific tests / Windows-only tests (arm64, 1/2) | Set up locked Python and test dependencies / Restore uv dependency downloads and builds | view step | 853.18 MiB | +0 B |
misses
none
Compressed archive bytes. Not-restored bytes use the main-size reference; missing sizes remain unknown.
[Medium impact] The PR appears safe to merge; the test cleanup finding is fixed and no new actionable issues remain.
|
This comment has been minimized.
This comment has been minimized.
…e a shared store The runner opens every store through hermes_state_registry.acquire(), which resolves the path, so on a symlinked state.db authority.db.db_path is already the link target: authority_home() derived from it still scoped cron to the store's directory in the real gateway. _build_profile_authority now records the reserved home on the authority and authority_home() returns it, falling back to the store's directory only for authorities built outside that path. File-identity ownership admits a linked store, so two homes linking one file would both build authorities and the second epoch would stale the first; refuse a store another served profile already owns. Regression tests now open through acquire() as the gateway does.
Fixes #145075
What was wrong
When a profile's
state.dbis a symlink to a folder outside the profile home (e.g.~/.hermes/state.db → ~/.hermes-db/state.db), every in-gateway cron run failed withProviderNotConfiguredErrorand "skill not found". The gateway worked out the profile home asPath(authority.db.db_path).resolve().parent.resolve()follows the link, so the cron run was scoped to~/.hermes-db/, which has no.env, noskills/and no credential pool. Config reads, the stored credential reference,/modelalias lookups, orphan-grace, the single-authority lookup and the groups/Bots owner check all used the same pattern.Fix
_build_profile_authorityrecords the reserved profile home on the authority (SessionAuthority.home), andsession_authorities.authority_home(authority)returns it. Every former.resolve().parentsite calls it:session_cronbind/unbind/execute,session_mutation_model×3,session_policy,session_policy_credentials×2,session_policy_refresh,session_viewer_close,authority_for_home,session_group_controls. Authorities built outside that path (single store, no reservation) fall back to the store's own directory, resolved.db_path: the runner opens every store throughhermes_state_registry.acquire(), which resolves the path. On a symlinked storedb_pathis already the link target, so no "don't resolve" variant can recover the home.profile_id: single-profile and test authorities carry a bare name ('default','p'), not a path.run_runtime._build_profile_authoritychecks store ownership by file identity (samefile(home/'state.db')), so a symlinked store is no longer refused as "does not belong to the reserved profile". It also refuses a store another served profile already owns: two homes linking one file would otherwise both build authorities, and the second epoch would make the first one's admissions stale.samefile().Salvage credit
Changes on top
authority_home()used the unresolveddb_pathparent, which only works when the caller opened the store through the link. The real runner opens it viaacquire()(resolved), so cron still scoped to the store directory there (Greptile caught this). The regression fixtures now open throughacquire()as the gateway does.samefilecheck).session_group_controls.dispatch_group_controlrefused every groups/Bots call on a symlinked store withprofile_mismatch.Test evidence
Red on
origin/main, green on this head. 13 tests intest_session_cron_symlinked_store.py,test_authority_home_symlinked_store.py(sibling sites + group controls) andtest_profile_authority_symlinked_store.py(boot ownership + shared store):Wider sweep: every test file in
tests/{gateway,tui_gateway,tools,acp_adapter,plugins}that touches the changed modules or builds aSessionAuthority(437 files) gave 2608 passed, 0 failed, 8 skipped.Live probe on an isolated HERMES_HOME (
hermes-home/state.db → ../hermes-db/state.db, with an OpenRouter key inhermes-home/.envand one skill). It went through the realacquire(), the realrun_runtime._build_profile_authority→initialize_session_authority→bind_owner, and the realsession_cron.execute+_profile_runtime_scope, then ranresolve_runtime_provider()inside the job:Infographic