Skip to content

feat(spawn): select per-project GitHub account for worker gh operations - #26

Closed
knowttl wants to merge 1 commit into
mainfrom
fm/fm-gh-account
Closed

feat(spawn): select per-project GitHub account for worker gh operations#26
knowttl wants to merge 1 commit into
mainfrom
fm/fm-gh-account

Conversation

@knowttl

@knowttl knowttl commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Intent

Add automatic per-project GitHub account selection for gh API operations to firstmate's worker spawn path (bin/fm-spawn.sh). Problem: worker gh calls (PR creation, gh-axi, gh api, the no-mistakes pipeline) authenticate with the token selected by GH_CONFIG_DIR, which defaults to ~/.config/gh. The captain runs two GitHub accounts isolated per project: knowttl (repos under github.com/knowttl and ssh alias github.com-knowttl; gh config at data/gh-config/knowttl) and pwxgh (repos under github.com/powerex-development and ssh alias github.com-pwxgh, org enforces SAML SSO; gh config at data/gh-config/pwxgh). The default account is not SSO-authorized for powerex-development, so gh pr create 403'd on pwxgh projects unless GH_CONFIG_DIR was set by hand. Git transport is already durable (SSH aliases + insteadOf + includeIf); only the gh CLI config selection was the gap. Goal: when firstmate launches a ship/scout worker, export GH_CONFIG_DIR at the correct per-account gh config into the worker's pane so every gh call uses the right account automatically. Required behavior: (1) resolve the account from the project's origin remote via a small explicit extensible mapping table (knowttl<-github.com-knowttl or github.com/knowttl; pwxgh<-github.com-pwxgh or github.com/powerex-development), no scattered hardcoded org names and no new config-file format; (2) export GH_CONFIG_DIR=/ where the base is the PRIMARY firstmate home's data/gh-config (absolute) so both a main-home crewmate and a secondmate-home crewmate reach the same authorized configs - a secondmate home resolves its primary via the local-route parent record (fm-secondmate-parent-lib.sh); (3) if the origin does not resolve to a known account, leave the environment unchanged - never guess; (4) do not break, reorder, or duplicate existing spawn env wiring - add alongside the GOTMPDIR export. Implementation intentionally kept to a single resolution helper (new bin/fm-gh-account-lib.sh) plus the spawn export, no model/policy layers. Guarded to KIND!=secondmate since a secondmate spawn is not project work; also gates on the account config dir existing to avoid pointing gh at a nonexistent store. Note (evidence-based divergence from the brief's parenthetical): the firstmate repo's real origin is git@github.com-knowttl:knowttl/firstmate.git, so it correctly maps to the knowttl account (knowttl owns the repo and is SSO-fine for it) rather than being treated as no-account; this is deterministic table matching, not a guess. Out of scope: git transport/credential.helper, no-mistakes pipeline internals, and how accounts are authenticated (tokens already exist and are SSO-authorized). Tests: added colocated tests/fm-gh-account.test.sh (10 cases) exercising the library's public functions - the same ones fm-spawn calls - against real origin URL forms, real temp git repos, and real fake home dirs (both ssh-alias and https forms per account, unrelated origin -> no account, secondmate reaching primary, missing-config, no-origin), asserting observable behavior not source bytes.

What Changed

  • Added bin/fm-gh-account-lib.sh, a resolution helper that maps a project's origin remote to a GitHub account via an explicit table (knowttl / pwxgh, matched on ssh-alias host or github.com namespace) and composes the GH_CONFIG_DIR under the primary firstmate home's data/gh-config, resolving a secondmate home to its local-route parent home so both reach the same authorized configs.
  • Wired the helper into bin/fm-spawn.sh: alongside the existing GOTMPDIR export, ship/scout spawns (KIND != secondmate) now export GH_CONFIG_DIR into the worker's pane when the origin maps to a known account and that account's config dir exists, leaving the environment unchanged otherwise.
  • Added tests/fm-gh-account.test.sh (10 cases) exercising the library's public functions against real origin URL forms, temp git repos, and fake home dirs, covering both ssh-alias and https forms per account, unrelated/no origin, secondmate-to-primary resolution, and missing config.

Risk Assessment

✅ Low: A well-bounded, additive change: one new self-contained resolver library plus a single guarded env export that matches existing spawn wiring, fully covered by behavioral tests, and it satisfies every required intent constraint with no reachable invariant violation.

Testing

Ran the colocated behavior suite (10/10 pass) which exercises the library's public functions the same way fm-spawn calls them, then reproduced the actual fm-spawn.sh export block against real git repos and fake homes to capture the worker-pane CLI transcript: a mapped knowttl/pwxgh project emits export GH_CONFIG_DIR=&lt;primary-home&gt;/data/gh-config/&lt;account&gt;, a secondmate home reaches the primary home's config, and unmapped-origin, secondmate-spawn, missing-config, and no-origin cases emit nothing (env left unchanged). This is a shell/CLI change with no UI surface, so the reviewer-visible evidence is the captured command transcript rather than a screenshot. All checks passed with no findings.

Evidence: Worker-pane transcript: real fm-spawn.sh GH_CONFIG_DIR export block across all account cases

[1] ship, knowttl -> export GH_CONFIG_DIR=.../data/gh-config/knowttl [2] scout, pwxgh (https) -> export GH_CONFIG_DIR=.../data/gh-config/pwxgh [3] ship, UNMAPPED -> (no export, env unchanged) [4] SECONDMATE spawn, pwxgh -> (no export, guarded out) [5] ship from SECONDMATE home, pwxgh -> export GH_CONFIG_DIR=<PRIMARY>/data/gh-config/pwxgh

=== Real fm-spawn.sh GH_CONFIG_DIR export block, driven against real repos + fake home ===

[1] ship worker, knowttl project (origin: git@github.com-knowttl:knowttl/firstmate.git)
  -> worker pane receives: export GH_CONFIG_DIR=/tmp/tmp.AaQ2kliFIw/home/data/gh-config/knowttl
  (done)

[2] scout worker, pwxgh project (origin: https://github.com/powerex-development/ops.cs.azure-epac.git)
  -> worker pane receives: export GH_CONFIG_DIR=/tmp/tmp.AaQ2kliFIw/home/data/gh-config/pwxgh
  (done)

[3] ship worker, UNMAPPED project (origin: git@github.com:someorg/repo.git) -> env left unchanged, no export
  (done)

[4] SECONDMATE spawn on the pwxgh project -> guarded out, no export (not project work)
  (done)

[5] ship worker from a SECONDMATE home, pwxgh project -> reaches PRIMARY home's config
  -> worker pane receives: export GH_CONFIG_DIR=/tmp/tmp.AaQ2kliFIw/home/data/gh-config/pwxgh
  (done)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-gh-account.test.sh — all 10 behavior cases pass (origin mapping across forms, config-base resolution, secondmate local/remote parent, missing-config, end-to-end spawn dir, unmapped, missing-account-config, no-origin)
  • Drove the real GH_CONFIG_DIR export block copied from bin/fm-spawn.sh (sourcing the actual bin/fm-gh-account-lib.sh) against real temp git repos and fake home dirs, capturing the exact line sent to the worker pane for ship/scout knowttl+pwxgh, unmapped, secondmate-guarded, and secondmate-home-reaching-primary cases
  • Verified KIND, FM_HOME, and PROJ_ABS referenced by the new spawn block are all defined in bin/fm-spawn.sh
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Worker gh calls (PR creation, gh-axi, the no-mistakes pipeline) authenticate
with the token selected by GH_CONFIG_DIR, which defaults to ~/.config/gh. That
default account is not SSO-authorized for every org, so gh pr create failed with
a SAML 403 on projects under an org that enforces SSO unless GH_CONFIG_DIR was
set by hand.

Resolve the account from the project's origin remote and export GH_CONFIG_DIR at
the matching per-account config in the primary firstmate home's data/gh-config
into each ship/scout worker's pane, alongside the existing GOTMPDIR export. A
secondmate-home worker reaches the same primary-home configs via the local-route
parent record, so main-home and secondmate-home workers use the same authorized
store. An origin that does not map to a known account, or a missing config,
leaves the environment unchanged rather than guessing an account.

Git transport and credential helpers are unchanged; this only selects the gh CLI
config directory.
@knowttl knowttl closed this Aug 12, 2026
@knowttl knowttl reopened this Aug 12, 2026
@knowttl

knowttl commented Aug 12, 2026

Copy link
Copy Markdown
Owner Author

Closing unmerged. This change hardcodes account-specific config into the generic firstmate repo; that belongs in local gitignored config, not tracked code. Kept for reference in this closed PR.

@knowttl knowttl closed this Aug 12, 2026
@knowttl
knowttl deleted the fm/fm-gh-account branch August 12, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant