Repository navigation
Team Environment: gh works through Lazurio for GitHub — personal sign-in refused in CLI and routes, the broker's identity shown, sign-out of a leftover personal account - #66
Conversation
…on's account left there can be signed out Principal 2026-09-28: a Team Environment (hosted-organization-team) works in GitHub through Lazurio for GitHub, set up by the Organization. One pure rule (src/tools/team-github.ts, githubActionRefused) refuses the curated gh sign-in and SSH key linking there and allows gh's sign-out only while the active account is a person's stored sign-in (never a [bot] App identity, an installation token or a token from a variable). The CLI reads the preset of the hosted operator Folder (hostedOperatorFolder, as lazurio update does); the Launchpad reads its Folder's preset per request. Refusals are the usual blocked result: reason team-environment, exit 2 / HTTP 409. The gh probe reports identity; the Team gh row offers Sign out only for a person's account and its status line says "uses Lazurio for GitHub". Docs: F19 addendum, environment tools, workspace presets, Launchpad development. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rompt and install sign nobody in gh's sign-in probe asks `gh auth status --json hosts` first (gh 2.81.0, cli/cli#11544), the one gh auth command the Organization's brokered gh answers, and falls back to the text form for an older gh. One field, never --show-token, so the token is not in the answer. The same reading serves the status, login confirmation, the scope check and the key removal (src/tools/gh-status.ts). On a Team Environment the App identity reads "Works as lazurio-for-github[bot]" next to "Uses Lazurio for GitHub", with no sign-out and a refused logout. `lazurio tools prompt gh` reads the hosted operator Folder's preset and prints gh's Team target state there instead of the sign-in; elsewhere the prompt adds the Team sentence. `tools install gh` on a Team Environment ends with the Team sentence instead of "Next: lazurio tools login gh"; the Launchpad's Team gh row offers "Install" only while gh is missing. The fake gh gains the broker mode and a gh without --json. Docs: F19 addendum, environment tools, Launchpad development; known limits kept. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking (exact head 8ea9ad1): an already-running gh device flow survives a Folder transition to hosted-organization-team. src/launchpad/server.ts:397-445 guards start and poll, but /api/update changes the preset without cancelling the in-memory session; src/tools/login.ts:577-607 continues the subprocess and, after device approval, writes a personal gh sign-in and proceeds to SSH key linking without another preset gate. A holder need not poll. I reproduced this in an isolated fixture: start gh under hosted-organization-personal, call updateProfile to hosted-organization-team, observe the new refusal, approve the fake gh flow without polling, and observe gh.state=octocat while the recorded preset remains hosted-organization-team. Please cancel pending gh sessions on transition to Team and/or recheck the current preset before completion and before SSH key effects, with a regression that does not poll after the switch. No real account or key was touched. The two required Platform checks (ubuntu-24.04 and macos-14) passed on this exact head; the optional Macroscope check was skipped. Focused local tests: 48 passed; this additional no-poll reproduction passed.
…y step that changes the account or the Machine Review of PR 66: a gh login started on a Work Folder finished, SSH key linking included, after the preset changed to hosted-organization-team. The login core now asks the rule again (LoginEnvironment.refused) after the device flow, around a scope refresh, before the key linking starts, before the key's creation, its registration, the known_hosts write and the final "signed in", and ends the session as blocked / team-environment. The Launchpad ends a running gh session when /api/update makes its Folder a Team Environment, and a gh poll there ends the session with the refusal. An unreadable preset fails the Launchpad session closed (environment-unreadable). Regression tests: each checkpoint in turn, and a Work-to-Team switch through the real profile update mid-login. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…environment-gh-rule # Conflicts: # docs/decisions.md
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty Exact-head re-review of 8d950e0: the prior Work-to-Team no-poll sign-in/key finding is addressed by the new checkpoints and the /api/update cancellation; the focused transition tests pass. I cannot approve while the required macOS check is red on this very commit (run 36421377210, job 108924664991; Ubuntu passes, Macroscope skipped). The macOS job fails at tests/launchpad-curated.test.ts:269: expected signed-in/SSH linked, got failed/environment-unreadable. In a fresh detached clone of this SHA with Bun 1.4.2 and host-wide heavy.lock, bun test tests/launchpad-curated.test.ts tests/update-path-entry.test.ts reproduces a failure of the same gh test twice (the response is operation-failed/recoveryMayBeRequired); bun test tests/launchpad-curated.test.ts alone passes. These are distinct observed failure results, not a proven shared root cause, but the multi-file check is not green even without concurrent host workloads. Please isolate and fix the cross-file interaction or make the gh flow deterministic, then rerun both required checks on the replacement exact head. No real account or SSH key was touched. Review only; no merge.
The flaky gh login test of PR 66 (launchpad-curated with update-path-entry, and macOS CI) was not shared test state: under load, the Launchpad's poll gate and the running gh session's Team-rule re-check read the Folder's preset at the same moment, and the Folder operation lock is exclusive and non-blocking (flock LOCK_NB), so one read was refused as busy: the poll answered operation-failed (or the session failed environment-unreadable). Reads (the Team rule's preset, the tools status, readFolderTools, /api/profile) now go through withFolderReadLock, which retries only on the new FolderOperationBusyError for up to 3 s; mutations keep the immediate refusal and the lock messages are unchanged. Regression tests: 24 parallel preset reads, a read waiting for a holder, the bound, and a Launchpad gh login completing while another reader keeps taking the lock. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty Exact-head re-review of e22a858: the prior cross-file failure is addressed by bounded retry of Folder read-lock contention. The read path retries only FolderOperationBusyError for at most 3 s; mutation paths still use the immediate-refusal operation lock. In a fresh isolated clone on Bun 1.4.2, the reproduction pair (launchpad-curated + update-path-entry) passed 3/3 in each order (13 pass, 0 fail each run); the focused tools-team-github/tools-login/launchpad-tools suite passed 52/52, including contention, bounded-read and mutation-refusal tests. git diff --check passed. Both required Platform checks on this SHA passed (Ubuntu and macOS; optional Macroscope skipped). No real account/key touched. Review only; do not merge.
…environment-gh-rule
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty Exact-head re-review of fc9d94d. The merge has parents e22a858 and main c624e73. Compared with e22a858, the five changed files are precisely the c624e73 main patch (identical stable patch-id); there is no additional PR-specific edit. In a fresh detached clone on Bun 1.4.2, lint and typecheck passed, and tools-team-github, launchpad-curated and update-path-entry passed together (34 pass, 0 fail). git diff --check passed. Both required Platform checks are SUCCESS on fc9d94d (Ubuntu and macOS); optional Macroscope is skipped. No unresolved conversations or pending review requests; base is current main and merge state is CLEAN. This renews the prior substantive approval on the merged exact head. Review only; do not merge.
Why
Decision of the Principal on 2026-09-28: there are three kinds of Environment (Personal, Work, Work Team), and a Team Environment works out of the box with the Organization's bot, the GitHub App "Lazurio for GitHub". A person's GitHub account is not signed in there and a person's SSH key is not linked. The earlier temporary exception for Team Machines ends.
The Launchpad already hid the gh actions on a Team Environment (#60), but the server and the CLI enforced nothing, and on a Machine with the Organization's broker the product showed gh as "Not signed in" although gh works.
What changes
src/tools/team-github.ts): on the presethosted-organization-teamthe sign-in of gh and the linking of an SSH key are refused; sign-out is allowed only when gh's active account is a person's stored sign-in. The CLI, the routes and the page use the same function.{kind: "blocked", reason: "team-environment", …}, exit 2 in the CLI, HTTP 409 from the server, with one wording in Czech and English.gh auth status --json hostsfirst (gh 2.81.0 and newer; the onlygh authcommand the broker's wrapper answers) and falls back to the text form for an older gh. The Team row says "Works as lazurio-for-github[bot] · Uses Lazurio for GitHub". The token is never requested (--show-tokenis never passed) and never reaches a result.lazurio tools prompt ghprints a Team prompt,lazurio tools install ghends with the Team sentence, the page offers "Install" and not "Install and sign in".How a command learns the kind of Environment
The Launchpad server reads the preset of its own Folder on every gated request.
tools login,logout,installandprompttake no Folder; on a hosted Machine they read the preset of the operator's Folder the waylazurio updatefinds it. Without such a Folder (a workstation) they behave as before.What does not change
Generated Folder instructions and the template revision. The two other hosted presets and the local Folder. The path through Lazurio for GitHub itself is delivered by the Machine; the Platform does not build it.
Known limits, stated plainly
Verification
bun run checkby the implementing agent on this head: 765 pass, 4 skip, 0 fail; public guard and standalone smoke pass. Fake tools, temporary HOME, a PATH without system directories. The delegating agent read the rule and the texts and did not re-run the check locally; the required checks on GitHub run it on both systems.Refs #57
🤖 Generated with Claude Code
Update 2026-09-28 (head e22a858): the flaky gh login test
Pablo's second review found the gh login test failing when
tests/launchpad-curated.test.tsran together withtests/update-path-entry.test.ts(and macOS CI red). Root cause, reproduced locally (about 1 in 6 runs of the pair): not shared test state but a race this PR introduced — the poll route's Team-rule check and the running gh session's re-check both read the Folder's preset under the Folder operation lock, which is exclusive and non-blocking (flock LOCK_EX|LOCK_NB), so under load one read was refused as busy and the poll answeredoperation-failed.Fix: reads (
folderPreset, tools status,readFolderTools,/api/profile) go throughwithFolderReadLock, which retries only on the newFolderOperationBusyErrorfor at most 3 s with a ≤100 ms backoff; mutations keep the immediate refusal and the lock messages are unchanged. Regression tests: 24 parallel preset reads, a read waiting for a holder, the 3 s bound, and a full Launchpad gh login completing while another reader keeps taking the lock (the pair passed 16/16 in both orders afterwards). Author's ownbun run checkon e22a858: lint, typecheck, all tests, check:public, build:proof, smoke:proof green (exit 0).