Skip to content

[WIP] do not review — feat(minimald): GitHub-integrated sessions (spec 10) - #1130

Draft
norrietaylor wants to merge 8 commits into
mainfrom
claude/minimald-github-prd-r6ew53
Draft

[WIP] do not review — feat(minimald): GitHub-integrated sessions (spec 10)#1130
norrietaylor wants to merge 8 commits into
mainfrom
claude/minimald-github-prd-r6ew53

Conversation

@norrietaylor

@norrietaylor norrietaylor commented Jul 31, 2026

Copy link
Copy Markdown
Member

WIP — do not review. Successor to #996 (closed), same branch. Opened to keep
CI running against a real toolchain while the remaining spec-10 surface lands.

Spec 10 — GitHub-integrated minimald sessions

Implements the PRD at docs/specs/10-spec-github-sessions/. Daemon-held GitHub App
auth (device flow), a mediated min git/min api facade so no token enters the
sandbox
, repo pre-priming at activation, scope consent + reuse-or-mint, and a
client-driven PR-on-exit prompt.

State

CI is green across all five lane aggregators. main is merged in; the branch
carries the whole crates/github engine (domain types, grant store, device flow,
REST client, leak-proof gitops, refresh state machine, mock GitHub), the daemon
GithubService wiring and auth RPCs, the git% facade, and the
min github login|status|logout CLI.

Fixed while getting CI green

  • Errorable decoded every daemon error as a blank Ok. The enum is
    #[serde(untagged)] with Ok tried first, and the response types default all
    their fields, so an {"error": ..} payload deserialized as Ok(Default).
    min github login against an unconfigured daemon printed Open and enter code
    and then error: missing login_id instead of naming the unset variable. This
    also hid errors on ConfigureLoadout and FinalizeSession. Err is now
    declared first, with a regression test; verified end-to-end against a live
    VM-backed daemon.
  • The GitHub App client id is now baked in at build time. An installed user
    had no way to supply MINIMALD_GITHUB_CLIENT_ID: no daemon config file,
    minimald is autospawned rather than run from a service unit, and on macOS it
    runs as the microVM's init with an empty environment (vm.rs forwards only
    RUST_LOG). crates/github/build.rs registers the rebuild trigger,
    config.rs reads it via option_env!, and release.yml forwards the repo
    variable. Runtime env still overrides, so the mock server and GHES keep working.
  • install_url test asserted public GitHub's host while running against a mock.
  • Dead-code assert_no_token_on_disk gated on test-support.
  • E0382 in the minimald-rpc tests; unused futures::StreamExt in env.rs.

Not yet implemented (follow-up)

api% facade verb, in-sandbox min git helper, push/PR + prime RPC handlers,
activate consent/prime wiring, PR-on-exit, redaction + no-token proof harnesses,
integration/e2e suites, docs. Deferred by design: MCP server proper, on_destroy
executor, egress enforcement, NG1–NG6.

Needs a maintainer action before the feature works for users

Set the MINIMAL_GITHUB_CLIENT_ID repository variable (Settings → Secrets and
variables → Actions → Variables). A GitHub App client id is public — the device
flow is a public-client flow with no client secret — so it is deliberately a
variable, not a secret. Unset builds ship inert and fail closed.

🤖 Generated with Claude Code

Note

Add GitHub-integrated sessions with device-flow auth to minimald and min CLI

  • Introduces a new github crate (Cargo.toml) providing domain types, OAuth device-flow client, token/secret handling, scope validation, on-disk grant storage with strict Unix permissions, and a feature-gated test mock server.
  • Adds min github login|status|logout CLI subcommands in crates/minimal/src/github.rs; login initiates device-flow via the daemon, polls for completion, and prints the verification URL and user code.
  • Wires five new GitHub RPCs (GithubBeginLogin, GithubPollLogin, GithubStatus, GithubListAuths, GithubLogout) through minimald-rpc and minimald rpc dispatch to a shared GithubService initialized at daemon startup.
  • Extends mfile sessions with an optional [session.github] block (crates/mfile/src/lib.rs) carrying repos and scopes, validated on access rather than at parse time.
  • In-sandbox git% facade requests are now dispatched via SessionChannel::git_facade and proxied to daemon-held GitHub credentials; unauthenticated sessions receive a clear error.
  • Fixes Errorable serde variant ordering so {"error":…} payloads decode as Err instead of a defaulted Ok.
  • min attach now spawns ssh as a child process and waits for it (rather than exec-ing), ignoring SIGINT/SIGQUIT in the parent and propagating ssh's exit code using shell signal conventions.

Macroscope summarized 2e89634. (Automatic summaries will resume when PR exits draft mode or review begins).

@CLAassistant

CLAassistant commented Jul 31, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8202fda5-5f1b-4e42-b846-efd7a77f4b05

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

norrietaylor and others added 8 commits July 31, 2026 14:30
Spec 10. Daemon-held GitHub App auth over the OAuth device flow, a
mediated git/api facade so no token ever enters the sandbox, repo
pre-priming at activation, scope consent with reuse-or-mint, and a
client-driven PR-on-exit prompt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Domain types, an on-disk grant store (0600, atomic writes), the OAuth
device-flow client, a REST client (authenticated user, App installation,
pull requests), leak-proof `gitops` (env-only credential injection plus an
option-injection guard), and the token-refresh state machine (single-flight,
write-ahead rotation). `test-support` ships an in-process mock GitHub and
the `github-mock` binary so the daemon's auth and git paths can be
exercised without touching real GitHub.

Each consumer's manifest declares the dependency here rather than in its
own commit, so `Cargo.lock` is coherent with every manifest from this
commit on and the series stays bisectable.

Default features are I/O-free — no reqwest — so `mfile` and `minimal` can
depend on the crate without pulling an HTTP stack.

The App client id is baked in at build time: `build.rs` registers
`MINIMAL_GITHUB_CLIENT_ID` as a rebuild trigger so a rotated id is never
served from a stale build cache, and `config.rs` reads it via
`option_env!`. Runtime `MINIMALD_GITHUB_CLIENT_ID` still takes precedence,
for GHES, the mock server, and self-hosted Apps. An installed user has no
other channel: there is no daemon config file, `minimald` is autospawned
rather than run from a service unit, and on macOS it runs as the microVM's
init with an empty environment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`GithubBeginLogin`, `GithubPollLogin`, `GithubStatus`, `GithubListAuths`,
and `GithubLogout`.

Also fixes `Errorable`, which hid every daemon-side error. It is
`#[serde(untagged)]`, so serde tries variants in declaration order, and the
response types default all their fields — so with `Ok` first a daemon
`{"error": ".."}` payload decoded as `Ok(Default::default())` and the client
reported a blank success for a refused RPC. `min github login` against an
unconfigured daemon printed an empty verification URI instead of naming the
unset variable. `Err` is now declared first; no response type carries an
`error` field, so this is unambiguous. The bug also affected
`ConfigureLoadout` and `FinalizeSession`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Per-repo specs and scope overrides for repo pre-priming, validated at load
time so a malformed scope is a config error rather than a runtime auth
failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… facade

Builds one `GithubService` at daemon startup and shares it across every
session, dispatches the five auth RPCs through the `serve!` macro, and adds
the `authorize()` choke point over session attrs.

The `git%` facade isolates the token-bearing git leg from everything the
sandbox controls: daemon-side git runs only in a daemon-authored bare
mirror, with global and system config denied, and object transfer happens
via `objects/info/alternates` plus plain-file ref writes rather than a git
remote the sandbox could redirect.

`APP_SLUG` names the org's registered App and builds the installation URL
`min github status` prints; a mismatch there is invisible from inside the
daemon, since only that URL breaks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drives the device flow through the daemon over SSH RPC, polling with a
terminal spinner until the login resolves.

Attach now runs ssh as a waited-on child rather than `exec()`ing into it, so
control returns to the client once the session shell exits — the seam
post-attach flows such as an exit prompt hook into. Keyboard signals are
ignored while the child runs so Ctrl-C reaches the foreground process
instead of killing the waiting client, and the child's status is propagated
so the observable exit code matches the old `exec()`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Forwards the `MINIMAL_GITHUB_CLIENT_ID` repository variable from a
workflow-level `env` block, so it reaches every build job — including the
`minimald-linux-*` binaries that `build-release-initramfs` repacks as the
guest daemon, which boots with an empty environment and could not be
configured any other way. The reading side is `crates/github`.

A repository variable, not a secret: a GitHub App client id is public,
because the device flow is a public-client flow with no client secret.
Unset resolves to the empty string, which reads as "no App configured", so
a release cut before the variable is set still builds and ships inert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The workspace is 30 crates now; the map still said 29 and omitted the row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@norrietaylor
norrietaylor force-pushed the claude/minimald-github-prd-r6ew53 branch from 2e89634 to 295dfc3 Compare July 31, 2026 21:31
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.

2 participants