8000 feat(bep): renew an expiring GitHub sign-in once at the proxy by minimal-foundry[bot] · Pull Request #2518 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

feat(bep): renew an expiring GitHub sign-in once at the proxy - #2518

Merged
norrietaylor merged 10 commits into
mainfrom
agent/2279-renew-an-expiring-sign-in-once-at-the-proxy
Oct 25, 2026
Merged

norrietaylor merged 10 commits into
mainfrom
agent/2279-renew-an-expiring-sign-in-once-at-the-proxy

Conversation

@minimal-foundry
Copy link
Copy Markdown
Contributor

T24 — Renew an expiring sign-in once at the proxy

slice: S2
kind: task
covers: BEP-069
size: L
est: 350 code, 420 tests
executor: agent
difficulty: needs care
model: strongest
diagnostics: min box audit <box> shows each renewal as its own record, and a sign-in that cannot renew as a refusal marked github_sign_in_required
observability: one info log line per renewal naming the account and the new expiry, and one warn line when GitHub refuses it
layers:
- renew:
- crates/bep/src/signin.rs: renews from the refresh material when the token has expired or expires within 5 minutes, writing the item update-only before first use
- crates/bep/src/forward.rs: one renewal in flight per item, the others waiting for its outcome; refuses when the write fails or the item is gone
- crates/bep/src/redeemer.rs: BoxRedeemer::credential no longer refuses an item whose standing is Live but expired; it hands the item to the renewal path (widened 2026-10-24)
- crates/bep/src/main.rs: wires a GitHub token-endpoint client into the proxy, carrying the app's client id and the MINIMAL_GITHUB_APP_CLIENT_SECRET the host passes it (widened 2026-10-24)
test: cargo nextest run -p bep an_expiring_sign_in_is_renewed_at_the_proxy
test: cargo nextest run -p bep concurrent_redemptions_share_one_renewal
test: cargo nextest run -p bep renewal_not_persisted_is_refused
test: cargo nextest run -p bep renewal_finding_item_deleted_refuses_and_creates_nothing
test: cargo nextest run -p bep a_sign_in_with_time_to_live_is_not_renewed
test: cargo nextest run -p bep unrenewable_sign_in_refuses_with_sign_in_required
- client:
- crates/minimal/src/cmd/auth.rs: reads only the item's metadata after sign-in
test: cargo nextest run -p minimal client_never_presents_or_refreshes_the_token
produces: none
consumes: none
proof: A box running past the token's 8 hours still clones and pushes, renewed once at the proxy, with no new sign-in.
depends on:
- blocked by #2276


Generated by Claude Code

Proof

A box request that redeems a GitHub sign-in whose token has expired, or expires within 5 minutes, now renews the sign-in once at the proxy. The proxy writes the renewed token to the same keychain item and substitutes it, so git and gh in a box that runs past the token's 8 hours keep working with no new min auth login. The tests prove this against a stand-in token endpoint. No test in this session called the real github.com token endpoint.

Notes from the agent: Audit record: audit.rs is not in the layers, so I did not add a new record kind for renewals. Each renewal is written as its own decision record, with outcome admit and marker github_sign_in_renewed, before the decision record of the request. A later task can add a dedicated kind in audit.rs.

Refusal markers: a failed keychain write and a deleted item both refuse the request with github_sign_in_required. The spec names no other marker for them. The text the box receives in the refusal is unchanged (the generic 'did not accept the sealed credential' body). I did not add a 'run min auth login' body, because a sign-in after a logout gets a new instance, and that does not heal a box that holds the old reference.

Behaviour the spec does not cover: if GitHub refuses an early renewal (the token is still valid but expires within 5 minutes), the proxy substitutes the held token and does not refuse the request.

Update-only write: Keychain::update is atomic in MemoryKeychain. For HostKeychain it uses the default, which checks the held instance and then writes. Neither security nor secret-tool has an update-only write, so a logout between the check and the write can still create the item again. The window is a few milliseconds.

main.rs: GitHubTokenEndpoint is a small synchronous HTTPS client built on rustls and std TcpStream. I wrote it this way so that Cargo.toml needs no new dependency. It POSTs to https://github.com/login/oauth/access_token with the App client id Iv23liv6KetAFJp4Eb5K.…[truncated]

Tests

line exit tail
cargo nextest run -p bep an_expiring_sign_in_is_renewed_at_the_proxy 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-c51b... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 11.66s ──────────── Nextest run ID 21b9e46c-b1c5-45b8-9137-57bedef0b3a4 with nexte
cargo nextest run -p bep concurrent_redemptions_share_one_renewal 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-c65c... Finished 'test' profile [unoptimized] target(s) in 0.25s ──────────── Nextest run ID 14cfa34d-ed18-456d-a4ad-c82048bd159b with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep renewal_not_persisted_is_refused 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-0682... Finished 'test' profile [unoptimized] target(s) in 0.28s ──────────── Nextest run ID 82bf934b-1f56-471a-9a4e-1a9e154ad4b2 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep renewal_finding_item_deleted_refuses_and_creates_nothing 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-ab54... Finished 'test' profile [unoptimized] target(s) in 0.26s ──────────── Nextest run ID ca991e1b-7e7b-43bb-9424-c4736bdbf481 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep a_sign_in_with_time_to_live_is_not_renewed 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-9d5a... Finished 'test' profile [unoptimized] target(s) in 0.25s ──────────── Nextest run ID 8284154f-88c0-477b-869d-9fc99e11a7f0 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep unrenewable_sign_in_refuses_with_sign_in_required 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-805f... Finished 'test' profile [unoptimized] target(s) in 0.28s ──────────── Nextest run ID 7fd09d9b-9d09-4658-8ee2-a229c9780c59 with nextest profile: default Starting 1 test across 2
cargo nextest run -p minimal client_never_presents_or_refreshes_the_token 0 Applying loadouts: default Running task runtime-implement-test-6 in session task-runtime-implement-test-6-1e73... Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling stdlib v0.0.22 (/workbenc

Verify

min task run agent-verify exited 101 under rustc 1.98.1, which the repository does not pin (1.97.0 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

the sandbox ran rustc (1.98.1), the repository pins 1.97.0 in rust-toolchain.toml; a failure here may be the newer toolchain's, not this change's
Applying loadouts: default
Running task runtime-implement-verify in session task-runtime-implement-verify-1b98...
    Checking rustls-webpki v0.103.16
    Checking args v0.0.1 (/workbench/crates/args)
    Checking ot v0.0.1 (/workbench/crates/ot)
    Checking paths v0.0.1 (/workbench/crates/paths)
   Compiling stdlib v0.0.22 (/workbench/crates/stdlib)
   Compiling version v0.8.0 (/workbench/crates/version)
    Checking switch v0.0.1 (/workbench/crates/switch)
    Checking rustls v0.23.46
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
    Checking diagnostics v0.0.1 (/workbench/crates/diagnostics)
    Checking async-dialog v0.0.1 (/workbench/crates/async-dialog)
    Checking wait-timeout v0.2.1
    Checking rusty-fork v0.3.1
    Checking tokio-rustls v0.26.5
    Checking rustls-platform-verifier v0.7.0
    Checking proptest v1.12.0
    Checking hyper-rustls v0.27.9
    Checking tonic v0.14.6
    Checking bep v0.0.1 (/workbench/crates/bep)
    Checking reqwest v0.13.5
    Checking tonic-prost v0.14.6
    Checking google-cloud-auth v1.17.0
    Checking opentelemetry-http v0.33.1
    Checking opentelemetry-otlp v0.33.0
    Checking google-cloud-gax-internal v0.7.20
    Checking mlog v0.0.1 (/workbench/crates/mlog)
    Checking google-cloud-longrunning v1.14.0
    Checking google-cloud-iam-v1 v1.13.0
    Checking google-cloud-lro v1.11.0
    Checking google-cloud-storage v1.20.0
    Checking common v0.0.1 (/workbench/crates/common)
    Checking boxes v0.0.1 (/workbench/crates/boxes)
    Checking sandbox v0.0.1 (/workbench/crates/sandbox)
    Checking mfile v0.0.1 (/workbench/crates/mfile)
    Checking minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
    Checking decode v0.0.1 (/workbench/crates/decode)
    Checking checkouts v0.0.1 (/workbench/crates/checkouts)
error: using `chunks_exact` with a constant chunk size
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    |
help: consider using `as_chunks::<2>()` instead
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
    = note: `-D clippy::chunks-exact-to-as-chunks` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::chunks_exact_to_as_chunks)]`

error: could not compile `minimald-rpc` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

Base

The base branch moved while the coder worked and was merged in before the tests ran. These files changed on BOTH sides since the branch point — read them first, because a rewrite from a stale reading can undo a merge without a conflict:

  • crates/bep/src/forward.rs
  • crates/bep/src/main.rs
  • crates/bep/src/redeemer.rs
  • crates/minimal/src/cmd/auth.rs

Closes #2279

A token that has expired, or expires within 5 minutes, renews from its
refresh material before substitution. One renewal runs per item, and
other requests wait for its outcome. The renewed material is written
update-only before first use. A failed write or a deleted item refuses
the request. The renewal is its own audit record. (BEP-069)
After sign-in, min auth reads only the sign-in item's metadata and sends
nothing to GitHub. Renewal is the proxy's work. (BEP-069)
8000
…xpiring-sign-in-once-at-the-proxy

# Conflicts:
#	crates/bep/src/forward.rs
#	crates/bep/src/main.rs
#	crates/bep/src/redeemer.rs
@coderabbitai
coderabbitai Bot commented Oct 24, 2026 •
Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4244108b-cc3d-4d6e-a7e8-2b7bca12e565

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
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@minimal-foundry
minimal-foundry Bot commented Oct 24, 2026 •
Copy link
Copy Markdown
Contributor Author

Reviewed: 1 finding, none blocking at b88a8b06758a.

Address the comments on the diff.

Clobber: none seen.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 4 findings, 4 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/signin.rs Resolved
Comment thread crates/bep/src/signin.rs Resolved
Comment thread crates/bep/src/main.rs Resolved
Comment thread crates/bep/src/signin.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: CONDITIONS · 3 conditions · the full ruling

The renewal path follows Gatehouse §6.10 line 839 in its shape, but three parts deviate. First, the host keychain write is check-then-write. A logout in that window brings the item back, so a revoked reference can come back. C1 adds a shared lock. Second, the renewal is written as a second admit decision. Line 839 says the renewal is audited as its own event, and C2 adds Kind::Renewal. Third, the proxy reads the App secret from an environment variable that no launcher sets, so a browser-flow sign-in never renews. C3 embeds it at build time, as min does.

Ruled at aca6293 against gominimal/arch@109541a48c31 by claude-opus-5-5.

Comment thread crates/bep/src/signin.rs Resolved
Comment thread crates/bep/src/forward.rs Resolved
Comment thread crates/bep/src/main.rs Resolved
A failed renewal is not retried for 60 seconds. The token exchange has
one 30-second deadline, the lookup included, and a waiter stops waiting
after 45 seconds. The host keychain's update-only write now checks and
writes under a lock file that the logout also holds.
A renewal at the proxy checks and writes the item under the same lock,
so it cannot write the item back after the logout deleted it.
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/signin.rs Resolved
Comment thread crates/bep/src/main.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: CONDITIONS · 1 condition · the full ruling

The renewal path conforms to Gatehouse §6.10 line 839: renew in the window, persist update-only before first use, single-flight, and refuse on a failed write or a deleted item. One gap remains. Line 839 says a browser-flow sign-in refreshes under the embedded published-App secret. bep reads that secret only from a run-time variable that no host code sets, but min embeds it at build time. Thus a browser-flow sign-in cannot renew at the proxy, and it refuses after 8 hours. Make the change in C1. Then the change conforms.

Ruled at 2f00ee4 against gominimal/arch@109541a48c31 by claude-opus-5-5.

Comment thread crates/bep/src/main.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on the runtime's review findings, pushed as 2f00ee48fbce.

line exit tail
cargo nextest run -p bep an_expiring_sign_in_is_renewed_at_the_proxy 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-a137... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 38.96s ──────────── Nextest run ID 3855c927-6699-4a4c-9eca-3e460b951a8b with nexte
cargo nextest run -p bep concurrent_redemptions_share_one_renewal 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-68d8... Finished 'test' profile [unoptimized] target(s) in 0.66s ──────────── Nextest run ID d6d9f9f1-0155-448a-b833-bf4acb68c605 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep renewal_not_persisted_is_refused 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-a8b6... Finished 'test' profile [unoptimized] target(s) in 0.81s ──────────── Nextest run ID 1d7da080-9e74-4883-b79e-f1ee8fa00e1b with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep renewal_finding_item_deleted_refuses_and_creates_nothing 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-8a24... Finished 'test' profile [unoptimized] target(s) in 0.55s ──────────── Nextest run ID 292fecd1-99e9-4eab-a2d0-f03f92a60963 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep a_sign_in_with_time_to_live_is_not_renewed 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-a5cf... Finished 'test' profile [unoptimized] target(s) in 0.93s ──────────── Nextest run ID 73d7e0bd-3c4f-4c7e-8901-c280bf72a42a with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep unrenewable_sign_in_refuses_with_sign_in_required 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-71fe... Finished 'test' profile [unoptimized] target(s) in 0.73s ──────────── Nextest run ID fbc752fe-5a03-487d-a8a5-d90414e869f3 with nextest profile: default Starting 1 test across 2
cargo nextest run -p minimal client_never_presents_or_refreshes_the_token 0 Applying loadouts: default Running task runtime-implement-test-6 in session task-runtime-implement-test-6-536c... Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling stdlib v0.0.22 (/workbenc

Verify min task run agent-verify exited 101 under rustc 1.98.1, which the repository does not pin (1.97.0 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

the sandbox ran rustc (1.98.1), the repository pins 1.97.0 in rust-toolchain.toml; a failure here may be the newer toolchain's, not this change's
Applying loadouts: default
Running task runtime-implement-verify in session task-runtime-implement-verify-baaa...
    Checking rustls-webpki v0.103.16
    Checking args v0.0.1 (/workbench/crates/args)
    Checking ot v0.0.1 (/workbench/crates/ot)
    Checking paths v0.0.1 (/workbench/crates/paths)
   Compiling stdlib v0.0.22 (/workbench/crates/stdlib)
   Compiling version v0.8.0 (/workbench/crates/version)
    Checking rustls v0.23.46
    Checking switch v0.0.1 (/workbench/crates/switch)
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
    Checking diagnostics v0.0.1 (/workbench/crates/diagnostics)
    Checking async-dialog v0.0.1 (/workbench/crates/async-dialog)
    Checking wait-timeout v0.2.1
    Checking tokio-rustls v0.26.5
    Checking rustls-platform-verifier v0.7.0
    Checking rusty-fork v0.3.1
    Checking hyper-rustls v0.27.9
    Checking tonic v0.14.6
    Checking bep v0.0.1 (/workbench/crates/bep)
    Checking proptest v1.12.0
    Checking reqwest v0.13.5
    Checking tonic-prost v0.14.6
    Checking google-cloud-auth v1.17.0
    Checking opentelemetry-http v0.33.1
    Checking opentelemetry-otlp v0.33.0
    Checking mlog v0.0.1 (/workbench/crates/mlog)
    Checking google-cloud-gax-internal v0.7.20
    Checking google-cloud-longrunning v1.14.0
    Checking google-cloud-iam-v1 v1.13.0
    Checking google-cloud-lro v1.11.0
    Checking google-cloud-storage v1.20.0
    Checking common v0.0.1 (/workbench/crates/common)
    Checking boxes v0.0.1 (/workbench/crates/boxes)
    Checking sandbox v0.0.1 (/workbench/crates/sandbox)
    Checking mfile v0.0.1 (/workbench/crates/mfile)
    Checking minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
    Checking checkouts v0.0.1 (/workbench/crates/checkouts)
    Checking decode v0.0.1 (/workbench/crates/decode)
error: using `chunks_exact` with a constant chunk size
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    |
help: consider using `as_chunks::<2>()` instead
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
    = note: `-D clippy::chunks-exact-to-as-chunks` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::chunks_exact_to_as_chunks)]`

error: could not compile `minimald-rpc` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

Review threads the coder judged:

  • 👍 valid — crates/bep/src/signin.rs:548: The redeemer records each failed renewal by instance. For 60s after it, no request calls GitHub. The held token is used while it lives, and after it expires the request is refused github_sign_in_required. Test: a_failed_renewal_is_not_retried_within_the_back_off.
  • 👍 valid — PRRT_kwDOPdfcxM6v8j3t: HostKeychain::update now checks and writes under an exclusive lock on ~/.minimal/bep/sign-in.lock. min auth logout holds the same lock for its delete, so a renewal cannot write the item back after a logout. (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — PRRT_kwDOPdfcxM6v8j3w: GitHubTokenEndpoint::post has one 30s deadline for the lookup (on its own thread), the connect, the send and every read. SingleFlight waiters stop at a 45s wait limit (Ran::TimedOut), and the request is refused. Test: a_waiter_stops_waiting_at_the_wait_limit. (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — crates/bep/src/signin.rs:571: HostKeychain now overrides update with a check and a write under the item lock that the logout also takes. The update-only guarantee now holds for the host keychain too, and the doc comment says so. the_item_lock_excludes_a_second_holder tests the lock.

A box that runs past the token's 8 hours still clones and pushes. The proxy renews the sign-in once, and no new sign-in is necessary. In this round, a refused renewal is not retried on every request, a stalled renewal does not hold the waiting requests, and a renewal cannot write the item back after min auth logout deletes it.

Notes from the agent: Revise round 1. All four review threads are valid, and this round fixes each of them. The host keychain tools have no update-only write. For that reason, HostKeychain::update now checks and writes under an exclusive lock on ~/.minimal/bep/sign-in.lock (the proxy's directory). min auth logout holds the same lock for its delete in crates/minimal/src/cmd/auth.rs. That file is in the task's layers, but its layer note covers only the metadata read. If the logout cannot take the lock, it logs a warning and deletes anyway, because the delete is the enforcement. A renewal that cannot take the lock refuses as NotSaved. Resolved has a new failed field, and Ran has a new TimedOut variant. The new constants are RENEW_RETRY_AFTER_SECS = 60 in redeemer.rs and FLIGHT_WAIT_LIMIT = 45s in forward.rs. The token exchange keeps TOKEN_TIMEOUT = 30s, but it is now one deadline for the lookup, the connect, the send and every read. The lookup runs on a thread that the exchange stops waiting for. The only tests are a unit test of the lock itself. No test drives the real host keychain race. I did not run just ci or just clippy-strict. cargo clippy -p minimal --all-targets shows warnings in other crates (minimald, minimald-rpc). These come from the local rustc 1.98.1, which is newer than the 1.97.0 pin. They are not in the files this round changes.

Copy link
Copy Markdown
Member

/revise

Round 2 of #2518, on the arch condition at 2f00ee4 and the two review findings that stay open. No round is in flight.

  1. Arch C1 (thread 4368955003, and the review finding 4368951618 says the same): crates/bep/src/main.rs GitHubTokenEndpoint::new must take the client secret from the build, as crates/minimal/src/cmd/auth.rs BUILT_IN_SECRET does: option_env!("MINIMAL_GITHUB_APP_CLIENT_SECRET") is the default. Keep the run-time CLIENT_SECRET_ENV read only as an override. Change the doc comments on CLIENT_SECRET_ENV and on GitHubTokenEndpoint::new so they say where the secret comes from. Add a test in main.rs that pins it: a Flow::Browser renewal with the embedded secret unset is refused github_sign_in_required, and with it set the exchange carries it. The task's main.rs layer line already says "embedded at build time" (the issue body was amended on 2026-10-24).
  2. Review finding 4368951615 (signin::renew, line 585): when Keychain::update fails after GitHub accepted the refresh, the new pair is dropped and the item holds a refresh token GitHub already revoked. Keep the accepted pair for this request (substitute the new access token now) and record the write failure so the next renewal does not call GitHub with the dead refresh token; or state in the doc comment and in a reply why dropping it is the chosen behaviour, with the arch's note (2) in the 2f00ee4 ruling in mind.
  3. Reply on each of those three threads naming the commit, and resolve the ones the commit fixes.
  4. Do not touch the item lock, the back-off or the renewal audit record: the arch re-ruling at 2f00ee4 accepted those.

One or two commits, Conventional Commits subjects, body lines at most 100 characters.


Generated by Claude Code

The proxy took the GitHub App client secret only from the run-time
environment, and no launcher sets it, so a browser-flow sign-in never
renewed. The secret now comes from option_env!, as min's BUILT_IN_SECRET
does. A non-empty MINIMAL_GITHUB_APP_CLIENT_SECRET at run time overrides it.
GitHub revokes the old refresh token when it renews. When the item write
failed, the proxy dropped the new pair, and the item kept a dead refresh
token. The proxy now holds the pair in memory, and the next renewal writes
it in place of a call to GitHub. The request is still refused, and the
renewed token is substituted only after the write.
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — approval withheld

Verdict COMMENT — APPROVE withheld: 4 unresolved threads — minimal-arch[bot] crates/bep/src/main.rs, minimal-foundry[bot] crates/bep/src/signin.rs:660, minimal-foundry[bot] crates/bep/src/main.rs:968, minimal-arch[bot] crates/bep/src/main.rs. No findings.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: CONDITIONS · 2 conditions · the full ruling

The renewal path now keeps a renewed token and refresh pair in BoxRedeemer::unsaved after a failed keychain write, and it holds that pair across requests. Gatehouse §6.10 line 844 lets the proxy hold plaintext for the request only. Line 839 says that a persist failure refuses and that re-login recovers. The held pair also escapes the GitHub revoke in min auth logout. Remove the hold (C1), and change the test so that it proves the design's outcome (C2). The embedded browser-flow secret in 28c73e4c conforms.

Ruled at cecf11b against gominimal/arch@109541a48c31 by claude-opus-5-5.

Comment thread crates/bep/src/signin.rs Resolved
Comment thread crates/bep/src/signin.rs Resolved
Comment thread crates/bep/src/redeemer.rs Resolved
Comment thread crates/bep/src/redeemer.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a /revise note, pushed as cecf11b6b3fa.

line exit tail
cargo nextest run -p bep an_expiring_sign_in_is_renewed_at_the_proxy 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-c10f... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 23.47s ──────────── Nextest run ID 02a4bad7-58d2-4d2c-9f9a-6d19a2fd3770 with nexte
cargo nextest run -p bep concurrent_redemptions_share_one_renewal 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-a3e6... Finished 'test' profile [unoptimized] target(s) in 0.47s ──────────── Nextest run ID 37008350-fbaf-4ddf-90c8-e5c188ccbc48 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep renewal_not_persisted_is_refused 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-4089... Finished 'test' profile [unoptimized] target(s) in 0.45s ──────────── Nextest run ID 6691c3dc-627a-4a8e-aa77-f26c646bce92 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep renewal_finding_item_deleted_refuses_and_creates_nothing 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-c4d5... Finished 'test' profile [unoptimized] target(s) in 0.52s ──────────── Nextest run ID ba346004-7e42-4b22-941d-55827e158c19 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep a_sign_in_with_time_to_live_is_not_renewed 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-8b40... Finished 'test' profile [unoptimized] target(s) in 0.47s ──────────── Nextest run ID 3357da47-6b35-4a78-a618-20ad5d53bd4e with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep unrenewable_sign_in_refuses_with_sign_in_required 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-1d4a... Finished 'test' profile [unoptimized] target(s) in 0.50s ──────────── Nextest run ID 2437f311-f74d-42d4-a99f-046fb67cc98b with nextest profile: default Starting 1 test across 2
cargo nextest run -p minimal client_never_presents_or_refreshes_the_token 0 Applying loadouts: default Running task runtime-implement-test-6 in session task-runtime-implement-test-6-ee2a... Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling stdlib v0.0.22 (/workbenc

Verify min task run agent-verify exited 101 under rustc 1.98.1, which the repository does not pin (1.97.0 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

12
    Checking hash32 v0.3.1
    Checking yasna v0.6.0
    Checking module-lattice v0.2.3
    Checking lru v0.18.2
    Checking kem v0.3.0
    Checking size v0.5.0
    Checking data-encoding v2.11.1
    Checking sponge-cursor v0.1.0
    Checking compression-core v0.4.33
    Checking op v0.0.1 (/workbench/crates/op)
    Checking sha3 v0.12.0
    Checking ml-kem v0.3.2
    Checking compression-codecs v0.4.45
    Checking ratatui-core v0.1.2
    Checking rcgen v0.14.10
    Checking heapless v0.9.3
    Checking mlog v0.0.1 (/workbench/crates/mlog)
    Checking ssh-key v0.7.0-rc.11
    Checking signal-hook-mio v0.2.5
    Checking generic-array v1.4.4
    Checking derive_more v2.1.1
    Checking russh-cryptovec v0.62.0
    Checking num-bigint v0.5.1
    Checking russh-util v0.52.0
    Checking managed v0.8.0
    Checking constcat v0.6.1
    Checking tinyvec_macros v0.1.1
    Checking tagptr v0.2.0
    Checking hex-literal v1.1.0
    Checking bitflags v1.3.2
    Checking moka v0.12.16
    Checking russh v0.64.1
    Checking smoltcp v0.14.0
    Checking tinyvec v1.12.0
    Checking minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
    Checking crossterm v0.29.0
    Checking async-compression v0.4.50
    Checking async-tar v0.6.1
    Checking line-clipping v0.3.8
    Checking object v0.40.0
    Checking is_executable v1.0.6
    Checking ratatui-widgets v0.3.2
    Checking clap_complete v4.6.11
    Checking hickory-proto v0.26.3
    Checking switch v0.0.1 (/workbench/crates/switch)
    Checking check v0.0.1 (/workbench/crates/check)
    Checking bep v0.0.1 (/workbench/crates/bep)
    Checking orchestrator v0.0.1 (/workbench/crates/orchestrator)
    Checking sysinfo v0.39.6
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
    Checking ratatui-macros v0.7.2
    Checking diagnostics v0.0.1 (/workbench/crates/diagnostics)
    Checking ratatui-crossterm v0.1.2
    Checking fuzzy-matcher v0.3.7
    Checking mctx v0.0.1 (/workbench/crates/mctx)
    Checking hashbrown v0.14.5
    Checking dashmap v6.2.1
    Checking inquire v0.9.4
    Checking ratatui v0.30.2
error: using `chunks_exact` with a constant chunk size
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    |
help: consider using `as_chunks::<2>()` instead
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
    = note: `-D clippy::chunks-exact-to-as-chunks` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::chunks_exact_to_as_chunks)]`

error: could not compile `minimald-rpc` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

Review threads the coder judged:

  • 👍 valid — crates/bep/src/main.rs: 28c73e4: the proxy now embeds the secret with option_env!("MINIMAL_GITHUB_APP_CLIENT_SECRET"), as BUILT_IN_SECRET in min does. The run-time variable is only an override.
  • 👍 valid — crates/bep/src/signin.rs:660: cecf11b: the accepted pair is kept in memory (signin::Unsaved) and the next renewal writes it without calling GitHub. It is not substituted for this request, because BEP-069 requires a refusal with nothing substituted when the write fails.
  • 👍 valid — crates/bep/src/main.rs:968: 28c73e4: the secret is embedded at build time, so no launcher needs to pass it. The test a_browser_sign_in_renews_under_the_embedded_secret pins the body and the refusal without it.
  • 👍 valid — crates/bep/src/main.rs: 28c73e4: option_env!("MINIMAL_GITHUB_APP_CLIENT_SECRET") is the default, the run-time CLIENT_SECRET_ENV is an override, and the doc comments say so.

A box that runs past the token's 8 hours still clones and pushes with no new sign-in. The proxy renews the sign-in once, and now a browser-flow sign-in renews too, under the App secret embedded at build time. A renewal that the keychain did not write is kept and written on the next renewal, so the item does not keep a refresh token that GitHub revoked.

Notes from the agent: Round 2. Commit 28c73e4: GitHubTokenEndpoint::new takes the secret from option_env!("MINIMAL_GITHUB_APP_CLIENT_SECRET") (BUILT_IN_SECRET), and a non-empty run-time CLIENT_SECRET_ENV overrides it. Doc comments now say where the secret comes from. New test a_browser_sign_in_renews_under_the_embedded_secret pins it. The refresh body is split into refresh_body/exchange so the test checks the body with no network call. Commit cecf11b: for finding 4368951615 I did not substitute the new token for the current request, because the BEP-069 sub-requirement says a renewal that is not written refuses the request and substitutes nothing (renewal_not_persisted_is_refused). Instead signin::renew_holding keeps the accepted pair as signin::Unsaved, BoxRedeemer holds it in a Mutex<Option>, and the next renewal writes it without calling GitHub. If that pair's token also needs renewal, the next renewal refreshes from the pair's refresh token, not the item's revoked one. The item lock, the back-off and the renewal audit record are unchanged. Item 3 of the directive (reply on threads and resolve them) is not possible from this session: the runtime posts the verdicts below, and resolution is the reviewer's decision. just clippy-strict was not run.

Copy link
Copy Markdown
10BC8
Member

/revise

Round 3 of #2518, on the arch ruling at cecf11b. No round is in flight. Commit 28c73e4 (the embedded secret) is accepted; commit cecf11b (the in-memory hold of an unsaved renewal) is refused, and the review finding 4368951615 that prompted it is ruled against by the design.

  1. Arch C1 (threads 4369081658, 4369081659, 4369081661): remove the hold. In crates/bep/src/signin.rs, delete Unsaved and renew_holding; renew drops the renewed pair on a failed write and returns RenewError::NotSaved; delete the *unsaved = Some(candidate) line and every *unsaved = pending restore; put the RenewError::NotSaved doc back to "The renewed token is discarded" and the warn text back to "the renewed GitHub sign-in was not saved; the renewed token was discarded". In crates/bep/src/redeemer.rs, remove the BoxRedeemer::unsaved field and its initialiser, and make credential call signin::renew again. Gatehouse section 6.10 line 844 lets the proxy hold plaintext for the request only, and line 839 gives a persist failure one outcome: refuse, with re-login as the recovery.
  2. Arch C2 (thread 4369081662): replace an_unsaved_renewal_is_written_on_the_next_renewal with a test of the design's outcome: after a write that SwitchedWrites refuses, the request refuses github_sign_in_required; after the back-off, the next request calls the token endpoint again with the item's refresh token, not a held one, and the item still holds the old material; assert the endpoint saw two calls.
  3. On the review thread 4368951615, reply that the dropped pair is the design's ruling (section 6.10 lines 839 and 844) and resolve it. Reply on each arch thread naming the commit.
  4. Keep everything else at cecf11b as it is: the embedded secret, the item lock, the back-off, the exchange deadline and the renewal audit record are accepted.

One commit, Conventional Commits subject, body lines at most 100 characters.


Generated by Claude Code

Revert the in-memory hold of an unsaved renewal (cecf11b). The proxy holds the
renewed plaintext for the request only, and a failed write refuses the request,
with a new sign-in as the recovery. renew drops the renewed pair and returns
RenewError::NotSaved, and BoxRedeemer::credential calls signin::renew again.

The new test shows that after a refused write the request is refused
github_sign_in_required. After the back-off, the next renewal asks GitHub again
with the refresh token in the item, and the item keeps its old material.
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/main.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 3 of 10 on a /revise note, pushed as b88a8b06758a.

line exit tail
cargo nextest run -p bep an_expiring_sign_in_is_renewed_at_the_proxy 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-eb7f... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 14.79s ──────────── Nextest run ID f52def47-8f3d-4be7-827f-587c237e3653 with nexte
cargo nextest run -p bep concurrent_redemptions_share_one_renewal 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-8994... Finished 'test' profile [unoptimized] target(s) in 0.34s ──────────── Nextest run ID d1c99457-fa12-402b-bbe1-7d3715d2dcde with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep renewal_not_persisted_is_refused 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-c00f... Finished 'test' profile [unoptimized] target(s) in 0.34s ──────────── Nextest run ID f753e49d-7429-4095-90db-78190e11913b with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep renewal_finding_item_deleted_refuses_and_creates_nothing 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-a624... Finished 'test' profile [unoptimized] target(s) in 0.32s ──────────── Nextest run ID 222710b9-c219-44a8-b606-2af4d78d248e with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep a_sign_in_with_time_to_live_is_not_renewed 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-b5b2... Finished 'test' profile [unoptimized] target(s) in 0.30s ──────────── Nextest run ID ff158e7a-7882-410e-a4e0-2346b3ae08e8 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep unrenewable_sign_in_refuses_with_sign_in_required 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-30d1... Finished 'test' profile [unoptimized] target(s) in 0.34s ──────────── Nextest run ID b8c2a24b-0916-49ab-8990-53c9cfea0f62 with nextest profile: default Starting 1 test across 2
cargo nextest run -p minimal client_never_presents_or_refreshes_the_token 0 Applying loadouts: default Running task runtime-implement-test-6 in session task-runtime-implement-test-6-5ef4... Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling stdlib v0.0.22 (/workbenc

Verify min task run agent-verify exited 101 under rustc 1.98.1, which the repository does not pin (1.97.0 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.


    Checking unicode-truncate v2.0.1
    Checking opentelemetry-appender-tracing v0.33.1
    Checking hash32 v0.3.1
    Checking kasuari v0.4.12
    Checking yasna v0.6.0
    Checking module-lattice v0.2.3
    Checking lru v0.18.2
    Checking kem v0.3.0
    Checking data-encoding v2.11.1
    Checking compression-core v0.4.33
    Checking size v0.5.0
    Checking sponge-cursor v0.1.0
    Checking compression-codecs v0.4.45
    Checking op v0.0.1 (/workbench/crates/op)
    Checking sha3 v0.12.0
    Checking rcgen v0.14.10
    Checking ml-kem v0.3.2
    Checking ratatui-core v0.1.2
    Checking mlog v0.0.1 (/workbench/crates/mlog)
    Checking heapless v0.9.3
    Checking ssh-key v0.7.0-rc.11
    Checking signal-hook-mio v0.2.5
    Checking generic-array v1.4.4
    Checking derive_more v2.1.1
    Checking russh-cryptovec v0.62.0
    Checking num-bigint v0.5.1
    Checking russh-util v0.52.0
    Checking constcat v0.6.1
    Checking managed v0.8.0
    Checking bitflags v1.3.2
    Checking tinyvec_macros v0.1.1
    Checking hex-literal v1.1.0
    Checking tagptr v0.2.0
    Checking russh v0.64.1
    Checking tinyvec v1.12.0
    Checking moka v0.12.16
    Checking smoltcp v0.14.0
    Checking minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
    Checking crossterm v0.29.0
    Checking async-compression v0.4.50
    Checking async-tar v0.6.1
    Checking line-clipping v0.3.8
    Checking object v0.40.0
    Checking is_executable v1.0.6
    Checking ratatui-widgets v0.3.2
    Checking clap_complete v4.6.11
    Checking switch v0.0.1 (/workbench/crates/switch)
    Checking hickory-proto v0.26.3
    Checking check v0.0.1 (/workbench/crates/check)
    Checking bep v0.0.1 (/workbench/crates/bep)
    Checking orchestrator v0.0.1 (/workbench/crates/orchestrator)
    Checking sysinfo v0.39.6
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
    Checking ratatui-macros v0.7.2
    Checking diagnostics v0.0.1 (/workbench/crates/diagnostics)
    Checking ratatui-crossterm v0.1.2
    Checking mctx v0.0.1 (/workbench/crates/mctx)
    Checking fuzzy-matcher v0.3.7
error: using `chunks_exact` with a constant chunk size
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    |
help: consider using `as_chunks::<2>()` instead
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
    = note: `-D clippy::chunks-exact-to-as-chunks` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::chunks_exact_to_as_chunks)]`

error: could not compile `minimald-rpc` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

Review threads the coder judged:

  • 👍 valid — crates/bep/src/signin.rs: b88a8b0: renew drops the renewed pair on a failed write and returns RenewError::NotSaved. The candidate hold and the restores are gone.
  • 👍 valid — crates/bep/src/signin.rs: b88a8b0: removed Unsaved and renew_holding from signin.rs. The NotSaved doc and warn text say the renewed token was discarded again.
  • 👍 valid — crates/bep/src/redeemer.rs: b88a8b0: removed the BoxRedeemer::unsaved field and its initialiser. credential calls signin::renew again.
  • 👍 valid — crates/bep/src/redeemer.rs: b88a8b0: replaced the test with an_unsaved_renewal_is_discarded_and_renewed_from_the_item. Both requests refuse github_sign_in_required, the endpoint sees two calls with the item's refresh token, and the item keeps its old material.
  • 👎 invalid — PRRT_kwDOPdfcxM6v8tkY: The design rules on this. Gatehouse section 6.10 line 844 lets the proxy hold plaintext for the request only, and line 839 gives a failed persist one outcome: refuse, with a new sign-in as the recovery. So dropping the pair is correct. b88a8b0 removes the hold that this finding prompted. (not posted on the thread: not an open thread on the pull request)

A box that runs past the 8-hour token life still clones and pushes. The proxy renews the sign-in once and writes it to the item, with no new sign-in. When the item write fails, the proxy discards the renewed token and refuses the request with github_sign_in_required.

Notes from the agent: One commit, b88a8b0. It reverts cecf11b exactly: Unsaved, renew_holding and BoxRedeemer::unsaved are gone, credential calls signin::renew, and the NotSaved doc and warn text are back to the 'discarded' wording. It also adds an_unsaved_renewal_is_discarded_and_renewed_from_the_item, which uses SwitchedWrites with writes refused on both requests. The test asserts both requests refuse github_sign_in_required, the endpoint sees two calls and gets the item's refresh token both times, and the item keeps the old material. Directive item 3 asks to resolve thread 4368951615. My instructions forbid resolving threads, so I did not. My verdict on it is now invalid, with the design reason, and the reviewer can resolve it. Commit 28c73e4 and the rest of the branch are unchanged. cargo clippy -p bep --all-targets is clean.

@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS · the full ruling

This head removes the in-memory hold of a renewal that the keychain did not save. A failed write now discards the renewed pair and refuses the request with github_sign_in_required. Gatehouse §6.10 line 839 says that a persist failure refuses, and that re-login recovers. I checked each clause of line 839 again on this head, and the change conforms. The PR description still says that a logout can recreate the item between the check and the write. The item lock removes that window, so correct the description.

Ruled at b88a8b0 against gominimal/arch@109541a48c31 by claude-opus-5-5.

@norrietaylor norrietaylor changed the title T24 — Renew an expiring sign-in once at the proxy feat(bep): renew an expiring GitHub sign-in once at the proxy Oct 25, 2026
@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Babysitter approval at b88a8b0: every merge gate passes at this head (arch PASS, zero open threads, every lane aggregator green, bot resolutions re-verified). Squash title feat(bep): renew an expiring GitHub sign-in once at the proxy and the squashed message pass commitlint. Queued for squash merge.


Generated by Claude Code

@norrietaylor
norrietaylor added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit f388b54 Oct 25, 2026
37 checks passed
@norrietaylor
norrietaylor deleted the agent/2279-renew-an-expiring-sign-in-once-at-the-proxy branch October 25, 2026 02:30
norrietaylor added a commit that referenced this pull request Oct 25, 2026
…reap

#2518 added the GitHub token renewal beside serve() and its tests at the
end of the test module. Both sides keep their additions: supervised()
before the renewal code, and the supervisor tests before the renewal
tests.

Claude-Session: https://claude.ai/code/session_01WhCC5TEBNzPeWgytRsU4rc
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.

T24 — Renew an expiring sign-in once at the proxy

1 participant

0