Repository navigation
Buddy: his ChatGPT sign-in and state through his own Hermes gateway, in the Launchpad and lazurio buddy (DEV-6676 PR 1, F63) - #635
Conversation
…he owner-only control socket (DEV-6676, F63) The Launchpad becomes one more local client of the hermes-gateway.service that already runs Buddy, as Hermes Desktop is: `identify` over the control socket in ~/.hermes (or the pointer to hermes-gw-<sha256(home)[:16]>/control.sock), then one `session-ticket` per request, sent to the gateway's loopback dashboard API as X-Hermes-Gateway-Ticket with no Origin and no profile query. The socket, the pointer and its directory are refused when linked, another account's or open to others, the home when anyone else may write to it; the API origin must be exactly http://127.0.0.1:<port> or http://[::1]:<port>. HTTP goes through node:http without an agent: Bun's fetch takes HTTP_PROXY even for a loopback address and keeps it for the process, which would hand the ticket to a proxy. Deadlines (5 s socket, 15 s HTTP), bounded answers, closed failure reasons, and one new ticket after a refused one; nothing else is sent twice. Tests run against a stand-in gateway whose answers were recorded from the real gateway of Lazurio/hermes-agent v0.22.4-lazurio.1 (live values replaced by synthetic ones). Nothing uses the client yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… sign-in at a time (DEV-6676, F63) Start, poll, cancel and sign-out call the dashboard routes Hermes's own LOGIN uses (/api/providers/oauth/openai-codex/start, /poll/<id>, DELETE /sessions/<id>, DELETE /openai-codex), so only Hermes's OAuth worker writes Buddy's sign-in into his auth store; never `hermes auth add`, and the Platform never reads that store. One sign-in at a time in memory, a new start cancelling the old one on Hermes first; the holder gets an opaque 32-hex handle while Hermes's session id stays in the process. The code and OpenAI's page pass only in their exact shapes (codexDeviceCode, codexDevicePage), Hermes's own words become closed reasons (expired, refused, lost, failed), and the journal names only the operation, the outcome and the reason. Buddy's state is a pure function of one fresh reading: works, waits, or unknown (not-running when the gateway does not run). There is no "needs attention" until Lazurio/hermes-agent#16 gives Hermes's card a relogin state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dy status, sign-in start and sign-out (DEV-6676, F63)
GET /api/buddy answers {kind: "buddy", state, reason?, signIn: {state}, locale}; POST
/api/buddy/sign-in/start, /poll, /cancel and /api/buddy/sign-out drive the sign-in core. They
exist only where the recorded entry names Buddy's origin (404 buddy-absent elsewhere, a
workstation included). Every POST, the poll included, passes the hosted same-origin rule of the
admission, so Buddy's own frame cannot call them; each takes exactly its JSON fields (413, 415,
405, 400 as the house style), and start and sign-out answer 503 buddy-unavailable without his
gateway. The page that uses them comes later.
The CLI shares the core: `lazurio buddy status`, `lazurio buddy sign-in start`, which holds the
sign-in as `lazurio tools login` does (the code on its first line, each change after it, Ctrl-C
cancels it on Hermes), and `lazurio buddy sign-out`, all JSON on stdout and only where the
Folder's binding records Buddy. It replaces the interim installer's signin-start.sh and
signin-wait.sh for operators. buddy-chat's usage test no longer treats `status` as unknown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…client of Buddy's Hermes gateway Records variant A of the DEV-6676 shaping (Buddy's gateway with a one-time ticket) against a transient dashboard on :9119, a CLI alone and reading Hermes's files, with the security rules, the client's path and origin rules, the sign-in's closed reasons, the state's truth table, the routes and the CLI, what was recorded from the real gateway and what is derived, the mutation checks and the honest limits. Names the gaps outside the Platform: Lazurio/hermes-agent#16 (a relogin state), #15 (Lazurio's words for a lost sign-in) and the installer's change I-a (Buddy's gateway runs before his sign-in). Proposed for Matěj's decision at review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Platform's own client and sign-in core ran on macOS against the real gateway of Lazurio/hermes-agent v0.22.4-lazurio.1, its control socket through the pointer: waits, then works with a sign-in Hermes's own writer saved; a start through node:http whose real code passed codexDeviceCode, its poll, the cancel and both sign-outs. F63's "Verified by" now says so; nothing else changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty — P1, changes requested on exact head ab519598b2d33a3dd7399d8b345b7212313d688a.
src/buddy/sign-in.ts:161-176,293-325: cancelOnHermes() discards both transport failures and HTTP failures, and its callers clear current and proceed as though Hermes had cancelled. If the cancel route returns HTTP 500 (or times out after the gateway was reached), a second start() creates another live Hermes session and forgets the old handle. signOut() then reports signed-out after a 200/409 from the provider route even though the uncancelled OAuth worker can later approve its code and write a fresh credential. Explicit cancel() likewise reports cancelled without proof. This breaks F63's one-sign-in-at-a-time and no-sign-in-after-sign-out promises, not just the local journal.
Reproduced in a disposable exact-head checkout with the recorded fake gateway: intercept session DELETE with HTTP 500; first and replacement start() both return pending, the gateway retains two sessions and the old handle returns none; signOut() returns signed-out; approving the retained old session makes the provider card signed in again. Keep the session addressable and do not report a successful cancellation/replacement/sign-out unless Hermes positively confirms the session is ended (or proves it absent); handle indeterminate failures closed. Cover failed cancellation on replacement, explicit cancel, sign-out, and expiry/malformed-response paths with gateway-fixture tests.
The existing five focused Buddy files pass 48/48 here under Bun 1.4.4; their happy-path cancellations do not exercise this failure. Gateway protocol, loopback/proxy rule, route admission, status table, and F63's recorded-versus-derived fixture were inspected against the pinned Hermes tag. CI is green, but neither it nor the listed mutation checks closes this case. Review only; no merge or release.
…or a sign-out without it changes nothing (#635 review) Pablo's review of #635 found that the cancel of a running sign-in discarded every failure: a replacement start, an explicit cancel and a sign-out then went on as if Hermes had cancelled, so an uncancelled OAuth worker could still save a sign-in after the Launchpad said it was replaced, cancelled or signed out. A sign-in now stays held and addressable until Hermes says it ended: its poll answers an ending or a 404, or its cancel answers {ok: true} for exactly that session (Hermes marks it cancelled under the lock its worker saves under) or {ok: false, message: "session not found"}, its recorded answer for a session it does not hold. Anything else proves nothing: - a replacement start starts nothing and the sign-in before stays the holder's; - an explicit cancel answers blocked (sign-in-running), or unavailable with the gateway's reason; - a sign-out signs nothing out until the held sign-in is confirmed cancelled; - a poll the gateway does not answer keeps the sign-in; after a restart Hermes's 404 ends it; - an answer of no known shape, and a sign-in Hermes still calls waiting 90 s past its code's end (the worker's last round), end only once a cancel is confirmed; - a start of no known shape stays held without a holder until its cancel is confirmed. The routes answer 409 {kind: "blocked", reason: "sign-in-running"} for such a start, cancel or sign-out, and 503 buddy-unavailable for a cancel without the gateway; the CLI's Ctrl-C says when the cancel was not confirmed. Gateway-fixture tests cover every unconfirmed cancel (HTTP 500, another session's confirmation, another ok:false, a 404, no JSON, a refused ticket, a timeout) on replacement, cancel, sign-out, expiry and malformed answers; 12 more mutation checks fail a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…635 review) F63's sign-in, routes and CLI points now say when a sign-in ends (Hermes's poll ending or 404, its confirmed cancel or its "session not found"), that a replacement, a cancel and a sign-out without that change nothing (409 blocked, 503 without the gateway), and how expiry and answers of no known shape end. The alternatives name the rejected first head and a local-clock expiry; the verified and mutation lists and the honest limits follow the new tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
P1, When a sign-in counts as ended. Only on Hermes's word, checked at
Anything else proves nothing. A lost or refused answer, a timeout, an HTTP error or an answer of no known shape all keep the sign-in held and addressable:
Your reproduction, now. With the session DELETE intercepted as HTTP 500:
Tests added (gateway fixture):
Mutation checks. 45/45 fail a test. That includes this fault itself (any cancel answer taken as an end) and 11 further guards, each removed alone:
Checks run:
|
agentrozjedemeai
left a comment
There was a problem hiding this comment.
P1 — A session not found response does not always prove that the Codex worker cannot still save credentials. At pinned Hermes tag v0.22.4-lazurio.1, hermes_cli/web_routers/oauth.py:796-800 GC removes any session older than 15 minutes from _oauth_sessions without setting sess["cancelled"]. The worker retains that same dict (:303-345), and _codex_cancelled (:177-181) checks only its flag. Its 15-minute poll deadline begins after session creation/user-code acquisition (:91-102, :245, :308-323); a last poll or token exchange may be in flight when another /start runs GC. The old session then yields poll 404 or DELETE HTTP 200 {ok:false,message:"session not found"} (:873-920), while the worker can still reach _save_codex_tokens. In this PR, src/buddy/sign-in.ts:235-241,331-332 treats either answer as proof of ending; replacement and sign-out may therefore proceed before the old worker saves again.
I executed the actual tagged _gc_oauth_sessions and _codex_cancelled functions against an old, still-pending worker-held session: GC removed it from the map, but the worker reference reported cancelled=False. Please fix the pinned Hermes provider contract so GC sets cancelled=True under _oauth_sessions_lock before removing a live session (and test the in-flight worker/save race), then update the fixture/pin and requalify the platform flow. A missing session must not count as a safe ending until that contract is true.
The original DELETE→500 reproduction now passes locally: replacement blocked with one session retained, the old handle remained pending, sign-out did not run, and approval subsequently reported signed-in (3 targeted tests passed; CI checks on this SHA green). This is a distinct boundary at the 15-minute cleanup. Review only; no merge while main is frozen.
…nstance or after its worker's last round (#635 review) Pablo's second review of #635: at Lazurio/hermes-agent v0.22.4-lazurio.1, Hermes's 15-minute clean-up (_gc_oauth_sessions, oauth.py:796-800) removes a session without marking it cancelled, while its worker keeps the same record and checks only that mark (:177-181, :303-345). A poll's 404 or a cancel's {ok: false, message: "session not found"} therefore no longer counts as an end on its own. It ends a sign-in only: - from another gateway instance: each client answer now carries the instance_id its ticket was bound to, and a sign-in keeps the one that started it; Hermes runs one runtime per process, so a new instance means the old worker's process is gone; - or once the worker's last chance to save has passed, on the Launchpad's monotonic clock from the start's answer: expires_in (900 s), one more poll_interval, one poll request (4 x 15 s), the token exchange (3 tries, 183 s), the save's lock (15 s) and a 60 s margin, 1223 s for the recorded answer (600 s assumed for an interval of no known shape). Until then a replacement, a cancel and a sign-out answer 409 blocked sign-in-finishing, a poll keeps waiting, and the CLI says so in words on stderr. The expiry cancel waits for the same bound instead of a separate 90 s grace, so a last-round approval is never cut off. Gateway-fixture tests cover the clean-up's window in the same instance (before and after the bound), another instance, the interval, an unknown interval and a wall clock set forward; 9 more mutation checks fail a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or after the worker's bound (#635 review) F63's sign-in point now says when a session Hermes no longer holds can no longer be saved: another gateway instance answers, or the worker's last round has passed, with every constant of that bound and its source line at v0.22.4-lazurio.1. The routes and CLI points name sign-in-finishing, the alternatives add the rejected "not found as proof", waiting for the fork's fix and a wall-clock bound, and the honest limits state the clock, timeout and process assumptions. The fork's own fix (the clean-up marks a session cancelled) is a separate Lazurio/hermes-agent change; F63 does not depend on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
P1, "session not found" taken as proof that a sign-in ended. Fixed. The PR head is now The Hermes-side fix is a separate fork PR. In it, The rule now. A poll 404, or a cancel's
Until one of those holds, a replacement start, a cancel and a sign-out answer 409 Tests added (gateway fixture). The fake's new
Mutation checks. 54/54 fail a test. The 9 new ones are:
F63 now records:
Checks run:
|
…ermes-agent#17 The fork change that marks a collected sign-in session cancelled under its lock is now a real pull request, approved for the release with #11, #13 and #14; F63 named it only generically. The rule itself is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty — P1, changes requested on exact head b7cca474c9748f2a62664b5cfc4a324f11193497.
The addition is arithmetically 1223 s for the recorded 900 s + 5 s + 60 s + 183 s + 15 s + 60 s, but it is not an upper bound on the worker's last save at the pinned Hermes tag. In hermes_cli/web_routers/oauth.py:339-345, the worker enters _profile_scope(session_profile) before the cancellation/save lock. _profile_scope waits on a process-wide threading.RLock with no timeout (hermes_cli/web_server_profiles.py:239,262-273); neither that wait nor filesystem writes in _save_codex_tokens (auth_codex.py:174-214) are bounded by AUTH_LOCK_TIMEOUT_SECONDS. httpx's 15 s timeout is per I/O phase, not a whole-request wall-time cap. A collected worker can finish token exchange, wait on the profile lock past the Platform's finishedBy, then acquire it and save.
At that time src/buddy/sign-in.ts:273-295,472-490 treats the same-instance {ok:false,message:"session not found"} as ended and reports sign-out while the old uncancelled worker may later save credentials. In an isolated exact-head checkout, a one-off fake-gateway probe with a collected worker delayed until after the 1223 s bound reproduced signOut() -> signed-out, then a late fake.approve(oldId) -> signedIn=true (1/1). This is a timing model, not a claim that a real gateway was held for 20 minutes. The tagged code supplies the unbounded wait that makes the model possible. Existing tests approve the collected worker before the bound and do not cover this ordering.
The instance fast path also needs a stronger process-death guarantee: gateway/run.py:5219-5241 releases the runtime lock before os._exit, so a new instance can in principle become ready while the old process and its daemon OAuth worker still exist. A distinct instance_id proves a new runtime, not by itself the old worker's death; F63 itself notes this overlap.
Please make a missing-session answer safe by a qualified Hermes cancellation contract (e.g. ship/pin and verify #17's cleanup under the save lock), or provide a truly enforced worker-completion/process-death bound. Keep the held sign-in and 409 until that proof exists, and add a regression with a save delayed beyond the proposed bound. The CLI's stderr and route's 409 behavior, and exact-head focused tests (50/50), lint, typecheck and public check pass locally; current CI is green. Those gates do not establish this liveness guarantee. Review only; no merge or release.
…ation contract; "not found" ends one only there (#635 review) Pablo's third review of #635: the 1223 s bound was not an upper bound at Lazurio/hermes-agent v0.22.4-lazurio.1. The worker waits without a timeout for the process-wide profile lock before its save, the save's writes are not bounded, and httpx's 15 s is per I/O phase. A swept worker could therefore save after the bound. The rule now rests on Hermes's cancellation contract, Lazurio/hermes-agent#17: the sweep, like the cancel, marks a session cancelled under the lock its worker checks before it saves. - The gateway advertises the contract in identify. The client reads it into every endpoint and answer; a call that requires it is never sent to a gateway without it ("outdated"). The member and value sit in one constant, oauthCancelContract, pending the fork's exact naming. - Without the contract a start starts nothing: 409 {kind: "blocked", reason: "hermes-outdated"}, and the CLI says "Buddy needs an update before he can be signed in here." Status, and a sign-out without a held sign-in, still work. - With it, a cancel's "session not found" and a poll's 404 end a sign-in at once, on the same instance or on another one after a restart. Without it they keep the sign-in held (hermes-outdated). - The time bound, its constants, the monotonic clock and the expiry cancel are gone. Hermes ends a sign-in itself, and the Launchpad waits for that. The fake gateway honours cancellation marks: a worker saves only an unmarked session, and its sweep marks one only under the contract. Tests cover the gate in the core, route and CLI, a swept worker trying to save after "not found" (it cannot), a downgrade across a restart, and another instance. Mutation checks now cover the gate (8 of them); the bound's checks are gone with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntract, Lazurio/hermes-agent#17 (#635 review) F63's sign-in point now gates the start on the contract the gateway advertises (hermes-outdated otherwise) and counts "session not found" and a poll's 404 as an end only under it. The bound and its constants are gone. The alternatives add the rejected time bound (Pablo's third review: the profile lock's wait, the save's writes and httpx's per-phase timeout are unbounded) and the instance fast path, and record that reading the fork's capability is now adopted. The honest limits name the residual window at a restart, where Hermes releases its runtime lock before os._exit, and state that Buddy's status is read live. #17 is listed among the gaps outside the Platform. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and F63 records the restart window as closed (#635 review) #17 at d1cf093e lists lazurio-oauth-cancel-contract-v1 in identify's capabilities, after upstream's three, while the gateway is ready (gateway/lazurio_oauth_contract.py). That is what oauthCancelContract already held, so its pending note goes and its comment cites the fork's definition. #17 also has every gateway exit go through release_gateway_runtime_lock, which first marks every pending sign-in cancelled under _oauth_sessions_lock and only then releases the runtime lock; a save still holding that lock after 1 s keeps the runtime lock until the process exits. F63 now says the restart window is closed by #17, so a successor's "not found" is as safe as the gateway's own. It names the two cases #17 does not cover, neither of which applies to Buddy's single-profile gateway with openai-codex: a secondary profile a multi-profile gateway stops serving (release_profile_home), and the Nous poller's config adjustment after a save made before the "not found". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
P1: the 1223 s bound is not an upper bound, and another instance is not proof that the old worker is gone. Fixed. The PR head is now The time bound is gone. Its constants, the monotonic clock, the expiry cancel and their tests are removed. As your review found, at
F63 records the bound as a rejected alternative, citing your third review. The rule now rests on Hermes's cancellation contract, which is the Hermes side of this fix: the separate fork PR Lazurio/hermes-agent#17 at
Regressions (gateway fixture). The fake now honours cancellation marks: a worker saves only an unmarked session, and its sweep marks one only under the contract.
Mutation checks. 50/50 fail a test. The 8 on the gate:
Checks run:
|
…t a local path Variant A (the Launchpad as a local client of Buddy's Hermes gateway) is the implementing architecture the Buddy lead decided under Matěj's mandate for Buddy on the web; the Organization Admin may overrule it. The header said it awaited a decision, and it named a draft file that exists only on one machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Approved on exact head ee20ebb. The start gate accepts only a ready, local Hermes identify advertising lazurio-oauth-cancel-contract-v1; an outdated gateway receives no start request (409 hermes-outdated), while status and sign-out without a held session remain available. Under the contract, poll 404 and DELETE "session not found" release the held sign-in; without it, they do not. I checked the fork contract at Lazurio/hermes-agent#17 d1cf093e: the sweep and runtime-lock release mark pending sessions under the worker save lock; a successor alone is no longer treated as proof. F63 drops the unsound 1223 s bound, records the rejected alternatives and the remaining single-profile/openai-codex limits, and clearly marks the fork release as a prerequisite. Local isolated checkout: 262/262 tests in the eight Buddy-named files passed under the host lock; exact-head Platform CI is green, and no unresolved review threads remain. This is review approval only, not authorization to merge, release, or unfreeze Platform main.
Motivation
Buddy signs in to ChatGPT with his own account, separately from the agents (root decision 0210). So far only the interim installer could do that. It started a temporary
hermes dashboardon127.0.0.1:9119for up to four hours, printed instructions and stopped. Anyone on the machine could read that dashboard's session token fromGET /. Začínáme and Nastavení → Buddy need Buddy's sign-in and his state from the Launchpad, and operators need them without that dashboard.This is PR 1 of Buddy's onboarding (plan DEV-6676; the shaping for the Buddy lead is in the Lazurio root at
drafts/DEV-6676-buddy-onboarding/SHAPING.md: §3.1 variant A, §3.2 to §3.5, §3.10 and PR 1 in §5). It is the Platform's side only: the client, the sign-in, the state, the routes and the CLI. Persona, UI, profiles and Telegram come in later PRs.Shaping decisions this follows
hermes-gateway.servicethat already runs Buddy, the way Hermes Desktop is. It takes a one-time ticket from the gateway's owner-only control socket for each call and sends it to the gateway's loopback dashboard API. Rejected:hermes auth add.What changes
src/buddy/hermes-gateway.ts, the local client, ported from Desktop'slocal-gateway.tsand Hermes's own discovery code.~/.hermes/gateway.sock, or a pointer tohermes-gw-<sha256(home)[:16]>/control.sock. The socket, the pointer and its directory are refused if they are a link, belong to another account or are open to others. The home is refused if anyone else can write to it.identifyfirst, then onesession-ticketper request.http://127.0.0.1:<port>orhttp://[::1]:<port>.node:httpwithout an agent. It sends noOrigin, noprofilequery and nothing beyond the ticket,HostandAccept.src/buddy/sign-in.ts: start, poll, cancel and sign-out through/api/providers/oauth/openai-codex/*.One sign-in at a time. The holder gets an opaque 32-hex handle; Hermes's session id stays on the server.
A sign-in starts only on a Hermes that keeps its OAuth cancellation contract (Lazurio/hermes-agent#17).
identify:lazurio-oauth-cancel-contract-v1listed incapabilitieswhile the gateway is ready, as #17 atd1cf093edefines it ingateway/lazurio_oauth_contract.py. The constantoauthCancelContractnames it.{kind: "blocked", reason: "hermes-outdated"}and starts nothing, so no worker exists that could save late. The CLI says "Buddy needs an update before he can be signed in here."A sign-in ends only once Hermes's worker certainly can no longer save one (Pablo's three reviews: fixed in
74766d59,db21135cand3663ab07). That is the case when:{ok: true}for exactly that session;{ok: false, message: "session not found"}) and the instance saying so keeps the contract, on the same instance or another one after a restart.Anything else (a lost answer, HTTP 500, a timeout, an unexpected shape, or "not found" without the contract) keeps the sign-in held and addressable. A replacement start, a cancel or a sign-out that needed it to end then changes nothing. Hermes decides when a sign-in expires; the Launchpad keeps no time bound.
The code and OpenAI's page pass only in their exact shapes (the existing
codexDeviceCodeandcodexDevicePage).Hermes's own error text never leaves. Answers carry only closed reasons:
expired,refused,lost,failed.src/buddy/status.ts:works,waitsorunknown, withunknown+not-runningwhen the gateway is not running.src/launchpad/buddy-routes.ts):GET /api/buddyreturns{kind: "buddy", state, reason?, signIn: {state}, locale}.POST /api/buddy/sign-in/start|poll|cancelandPOST /api/buddy/sign-out.buddyOriginevery route answers 404buddy-absent.hermes-outdatedwithout the contract.{kind: "blocked", reason: "sign-in-running"}(or"hermes-outdated") and changes nothing.buddy-unavailablewhen the gateway is unreachable.scope: "buddy-sign-in") records only the operation, the outcome and the reason.lazurio buddy status --json,lazurio buddy sign-in start --jsonandlazurio buddy sign-out --json, listed inbuddyHelpand the dispatch.docs/decisions.md: the A–D table, the security rules, what was recorded and what derived, the mutation checks and honest limits. It names hermes-agent#16, hermes-agent#15 and installer change I-a.Deviations from the brief, for the lead to confirm
No separate
sign-in pollandsign-in cancelCLI commands.sign-in startholds the sign-in the waylazurio tools logindoes:A later separate process could only reach Hermes's session through its id or the code written to a file. §3.10 keeps both out of every file and answer. The held sign-in still replaces
signin-start.shandsignin-wait.sh.The home's mode rule follows Desktop, not "no group or other access". The socket, pointer and pointer directory get no access for anyone else. The home must not be writable by anyone else, but reading and searching are allowed (
HERMES_HOME_MODE0750/0701), as Desktop and Hermes allow. Those bits grant nothing against the 0600 socket.node:http, notfetch(the shaping's §3.3 saysfetch). Bun'sfetchusesHTTP_PROXYeven for a loopback address and keeps it for the rest of the process (observed with Bun 1.4.2), so it would hand the ticket to a proxy. Hermes's own test client turns proxies off for this exact call (trust_env=False).What does not change
messages.tstext, no persona or profile, no Telegram, no change tolazurio buddy chat.buddy-chat.test.tschanges one line only:statusis no longer an unknown command.auth.jsonor.env.Tests
tests/fixtures/fake-hermes-gateway.ts,hermes-gateway-v0.22.4-lazurio.1.json).v0.22.4-lazurio.1:python -m gateway.runwith a private home, the same way Hermes's owntest_native_http_auth.pyruns it.identify(ready, starting, draining); tickets and the socket's refusals; provider cards signed out and signed in (written by Hermes's own_save_codex_tokens); a real start against OpenAI's public device-code endpoint, cancelled at once, with its poll, the cancel and the poll's 404; both sign-outs (200, 409); the 401s; the 403 for aprofilequery; the pointer fallback.v0.22.4-lazurio.1gateway. The socket was reached through the pointer, because the home was longer than a socket path allows. Results:waits, thenworksonce Hermes's own writer had saved a sign-in.node:httpreturned a real code that passedcodexDeviceCode, with OpenAI's page.none) and both sign-outs behaved as expected.cancelled).tests/buddy-hermes-gateway.test.ts:Origin, noprofilequery.HTTP_PROXYset, because Bun'sfetchkeeps the first proxy it sees and that would leak into later tests.identifyor ticket answer is refused.tests/buddy-sign-in.test.ts:lost.ok: false, a 404, no JSON, a refused ticket, a timeout.blocked.collect) marks one only under the contract, as Pill test: wait for the check to settle, not for the request count #17 does.hermes-outdated); another instance with the contract ends it aslost.tests/launchpad-buddy.test.ts:buddy-absenton a personal Environment without Buddy, on an Organization's Environment and on a workstation.blockedfor a start, a cancel and a sign-out while Hermes does not confirm a cancel (sign-in-running); 409hermes-outdatedfor a start without the contract, while status and sign-out are still answered.tests/buddy-cli.test.ts: the CLI's JSON and exit codes, a held sign-in until it ends, Ctrl-C cancelling it or explaining on stderr why it could not, the start refused in words without the contract, and usage errors.identify(the member holding the value or listing it; no other value counts) and never send a call that requires it to a gateway without it.fetchmutation fails the proxy test alone and that removing serialization fails the two-tab test.bun run lint,bun run typecheck,bun run check:public: pass.buddy-chat,buddy-t3-runtime,launchpad-chatandlaunchpad-theme, run by name: 92 pass, 0 fail.bun test: onafc3b30a, 4810 pass, 10 skip, 0 fail across 318 files in 1071 s. The known browser flake Flaky under a loaded CI run: public-vault "without its boot document, try-01 without a vault keeps Trezor hidden…" hits a TimeoutError #615 did not occur in this run.3c65e7a7after it changes only the contract constant's comment and F63's text; the Buddy files rerun on it: 92 pass.Risks and limits
not-runningandbuddy-unavailable.hermes auth addtext (A lost provider sign-in tells the person to run 'hermes auth add'; Lazurio needs its own words with a link to Settings hermes-agent#15). That is needed before people other than the pilots get Buddy.hermes-outdated). Status, sign-out and the installer's own sign-in are unaffected. Pill test: wait for the check to settle, not for the request count #17 is planned for the fork release with Product update: implementation of the simplified contract (F13) #11, Workspace presets: three hosted Machine profiles, derived from the handover, adopting an existing Folder #13 and Evidence: first release candidate v0.1.0-rc.1 through the real release path #14.docs/decisions.mdwill conflict with PR 2 (persona), which adds F64. Resolve it hunk by hunk.Review follow-up
Pablo's P1 at
ab519598(a cancel's failure was taken as success): fixed in74766d59, with F63 updated in6d3f3378. The fault no longer reproduces:blocked, and the gateway keeps only the first session.pending.signOut()answersblocked.Pablo's second P1 at
6d3f3378(a "session not found" or poll 404 taken as proof of an end, although Hermes's 15-minute clean-up doesn't mark the session cancelled): answered indb21135cwith a time bound and an instance fast path. Both were superseded by the third fix below.Pablo's third P1 at
b7cca474(the 1223 s bound isn't an upper bound, and another instance isn't proof that the old worker is gone): fixed in3663ab07, with F63 updated inafc3b30a, and the field and the closed window confirmed in3c65e7a7. The Hermes side is the separate fork PR Pill test: wait for the check to settle, not for the request count #17 atd1cf093e.Merge
Merging waits until the Platform freeze ends, after v0.1.11 ships. This PR does not touch any live Environment.
🤖 Generated with Claude Code