feat(cargo-aprz): discover tokens from GitHub CLI - #175
martin-kolinek wants to merge 16 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
There was a problem hiding this comment.
🟡 Changes recommended
Moderate issues remain around Windows batch-shim launching and production process-path test coverage.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds host-aware GitHub CLI token discovery to cargo-aprz, preserving credential precedence and Codeberg behavior.
Changes:
- Adds asynchronous
gh auth tokenfallback with Enterprise host support. - Updates credential integration, redaction, documentation, and test fixtures.
- Resolves CLI credentials without logging or caching tokens.
File summaries
| File | Summary |
|---|---|
crates/cargo-aprz/src/main.rs |
Updates CLI credential documentation. |
crates/cargo-aprz/README.md |
Documents credential discovery behavior. |
crates/cargo-aprz/docs/DESIGN.md |
Defines credential resolution and security guarantees. |
crates/cargo-aprz-lib/tests/support/mod.rs |
Supplies deterministic test credentials. |
crates/cargo-aprz-lib/tests/process_globals_integration.rs |
Updates process-global integration arguments. |
crates/cargo-aprz-lib/tests/hosting_provider_integration.rs |
Tests credential scoping and redaction. |
crates/cargo-aprz-lib/src/commands/mod.rs |
Registers the credentials module. |
crates/cargo-aprz-lib/src/commands/github_credentials.rs |
Implements token discovery and PATH resolution. |
crates/cargo-aprz-lib/src/commands/common.rs |
Integrates resolved GitHub credentials. |
Review details
Suppressed comments (1)
crates/cargo-aprz-lib/src/commands/github_credentials.rs:201
- The resolver includes
.BATand.CMDfrom the defaultPATHEXT, butquery_ghlaunches the resolved path directly withtokio::process::Command(lines 145-150) and deliberately does not use a shell. On Windows, a batch shim cannot be started byCreateProcessthis way, so a PATH that selectsgh.cmd/gh.batwill be treated as an unavailable GitHub CLI. Either exclude batch extensions from direct resolution or add a dedicated, safely handledcmd.exepath if batch shims are intended to work.
const DEFAULT_PATH_EXT: &str = ".COM;.EXE;.BAT;.CMD";
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report❌ Patch coverage is
❌ Your project status has failed because the head coverage (99.7%) is below the target coverage (100.0%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #175 +/- ##
========================================
+ Coverage 97.6% 99.7% +2.1%
========================================
Files 304 77 -227
Lines 69683 14571 -55112
========================================
- Hits 68016 14533 -53483
+ Misses 1667 38 -1629
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Keep native Anvil runs host-aware, bound and directly test the gh process bridge, ignore blank environment tokens, and reject Windows batch shims. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99e3-4546-847a-e30dd5cb18a4
Record the generated trees after native APRZ credential discovery moved into cargo-aprz. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99e3-4546-847a-e30dd5cb18a4
There was a problem hiding this comment.
🟡 Changes recommended
Container credential forwarding and invalid credential fallback issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
crates/cargo-aprz-lib/src/commands/github_credentials.rs:103
- When
GITHUB_TOKENis present but cannot be converted to UTF-8, this returns immediately and suppresses theghfallback. That value cannot be used as an HTTP credential, so it should be treated like an unusable/missing environment value and continue to the host-aware lookup; otherwise an invalid environment entry prevents the documented credential precedence from reachinggh.
let Ok(token) = token.into_string() else {
log::trace!(
target: LOG_TARGET,
"GitHub credential source {GITHUB_TOKEN_ENV} is not valid UTF-8; using anonymous access"
);
return None;
crates/cargo-aprz-lib/src/commands/github_credentials.rs:95
- This early return makes a blank or whitespace-only
--github-tokenauthoritative, so the client receives an unusabletokencredential instead of continuing toGITHUB_TOKEN,gh, or anonymous access. The new CLI documentation says blank credentials are ignored, so apply the same trim-and-empty check used for the environment value here.
if let Some(token) = explicit {
log::trace!(target: LOG_TARGET, "GitHub credential source: --github-token");
return Some(token.clone());
}
- Files reviewed: 17/18 changed files
- Comments generated: 1
- Review effort level: Lite
Keep GITHUB_TOKEN visible in the executable APRZ plan without selecting or mutating native credentials, so the container driver continues forwarding its host-derived token. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99e3-4546-847a-e30dd5cb18a4
There was a problem hiding this comment.
🔵 Needs a closer look
Two moderate credential-discovery and fallback-warning issues remain unresolved.
Review details
Suppressed comments (2)
crates/cargo-anvil/templates/justfiles/anvil/checks/aprz.just:26
- With both credentials unavailable, this recipe now only performs the no-op and then runs anonymously, but it no longer emits the actionable warning that the previous recipe showed.
CommonArgsdefaults logging tonone(crates/cargo-aprz-lib/src/commands/common.rs:107-109), while the new discovery diagnostics are trace-level, so a native full-workspace run can exhaust the 60-request quota described above and wait without telling the user to rungh auth loginor setGITHUB_TOKEN. Preserve an equivalent visible warning for the anonymous fallback, outside the disabled logger.
# Load-bearing no-op: the container driver scans the executable dry-run
# plan for this variable before deriving a token for its gh-less image.
$null = $env:GITHUB_TOKEN
crates/cargo-aprz-lib/src/commands/github_credentials.rs:102
- This branch makes an invalid-UTF-8
GITHUB_TOKENa terminal anonymous decision. Such a value is not a usable nonblank token, so a validghlogin is skipped even though the documented precedence says the CLI fallback applies when no usable environment token is available. Treat this case like a missing/blank value and continue to hostname-aware discovery.
let Ok(token) = token.into_string() else {
log::trace!(
target: LOG_TARGET,
"GitHub credential source {GITHUB_TOKEN_ENV} is not valid UTF-8; using anonymous access"
);
- Files reviewed: 17/18 changed files
- Comments generated: 0 new
- Review effort level: Lite
Resolve the Anvil manifest conflict by regenerating the combined catalog while preserving APRZ host-aware discovery and container token forwarding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99e3-4546-847a-e30dd5cb18a4
There was a problem hiding this comment.
🔵 Needs a closer look
Two moderate credential-discovery issues remain in output validation and whitespace-only GITHUB_TOKEN handling.
Review details
Suppressed comments (2)
crates/cargo-aprz-lib/src/commands/github_credentials.rs:153
- Only non-empty UTF-8 is checked here, so a successful
ghwrapper that emits a multi-line or control-character value is returned as a credential.Client::newthen builds theAuthorizationheader from it (crates/cargo-aprz-lib/src/facts/hosting/client.rs:96-108) and propagates the header-parse error throughCommon::new, making this best-effort fallback abort instead of continuing anonymously. Validate the candidate as a legal header value before returning it.
let token = stdout.trim();
if token.is_empty() {
crates/cargo-aprz-lib/src/commands/github_credentials.rs:225
- The whitespace-only
GITHUB_TOKENbranch deliberately falls through togh, but thisCommandinherits that original environment because it never removes the variable. GitHub CLI treats any non-emptyGITHUB_TOKEN(including whitespace) as higher priority than its stored host credential, sogh auth tokencan echo the whitespace, which the latertrim()turns into anonymous access instead of the saved login. RemoveGITHUB_TOKENfrom this child environment so blank values truly fall through to the CLI login.
let child = Command::new(executable)
.args(args)
.stdin(stdin.into_stdio())
.stdout(stdout.into_stdio())
.stderr(stderr.into_stdio())
- Files reviewed: 17/18 changed files
- Comments generated: 0 new
- Review effort level: Lite
Add --github-token-from-gh and prevent both native and containerized commands from invoking the host GitHub CLI unless the command explicitly requests that credential source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
There was a problem hiding this comment.
🔵 Needs a closer look
Invalid UTF-8 GITHUB_TOKEN values currently prevent the documented gh fallback.
Review details
Suppressed comments (1)
crates/cargo-aprz-lib/src/commands/github_credentials.rs:111
- When
GITHUB_TOKENis present as an invalid UTF-8OsString, this branch returns immediately and skips the explicitly requestedghfallback. That contradicts the documented precedence of usingghwhen no usable/nonblank environment token is available; a validghlogin is silently replaced by anonymous access. Treat the invalid environment value as absent and continue to the opt-in discovery path.
if let Some(token) = read_environment() {
let Ok(token) = token.into_string() else {
log::trace!(
target: LOG_TARGET,
"GitHub credential source {GITHUB_TOKEN_ENV} is not valid UTF-8; using anonymous access"
);
return None;
- Files reviewed: 21/22 changed files
- Comments generated: 0 new
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
Critical host-selection and moderate process-tree termination issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
crates/cargo-aprz-lib/src/commands/github_credentials.rs:245
- The native timeout only drops
wait_with_output()and relies onkill_on_drop(true), which terminates theghchild but does not apply the container implementation's recursive process-tree termination ($process.Kill($true)). Aghexecutable that has spawned a helper can therefore leave that descendant running after the ten-second deadline, contrary to the process-tree termination contract described for this feature. Use a platform-appropriate process-group/tree termination strategy for the native path, or narrow the contract and add coverage for the intended behavior.
- Files reviewed: 23/24 changed files
- Comments generated: 1
- Review effort level: Lite
Bind Just-plan opt-in and endpoint parsing to one command, use process startup for Unix executable validation, and isolate environment-dependent credential integration in a child process. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
There was a problem hiding this comment.
🔵 Needs a closer look
Address the non-executable PATH candidate handling and terminate descendant processes after native timeouts.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
crates/cargo-aprz-lib/src/commands/github_credentials.rs:245
- The native timeout only drops
Childafterwait_with_outputexpires;.kill_on_drop(true)addresses that child, while this path has no process-group/job-object handling like the container implementation'sKill($true). Aghexecutable that spawns a descendant can therefore leave that descendant running (and holding inherited stdout/stderr resources) after the documented ten-second deadline. Terminate the complete process tree using a platform-appropriate process-group/job mechanism, and add a descendant-survival assertion to the native timeout test.
crates/cargo-anvil/templates/justfiles/anvil/container.just:982
Resolve-AnvilGhExecutablereturns the first regularghfile without checking whether it is executable. If an earlier explicit PATH entry contains a non-executable file and a later entry contains the real executable,Process.Start()fails for the first path and the wrapper falls through anonymously instead of continuing PATH lookup; the native resolver avoids this by filtering executability before selecting a candidate (crates/cargo-aprz-lib/src/commands/github_credentials.rs:277-289). Check executability before returning, or continue scanning after a failed candidate, so a shadowing non-executable file does not suppress a valid latergh.
foreach ($name in $names) {
$candidate = Join-Path $directory $name
if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) { continue }
# Process.Start below is the portable executable check. A Unix
# file without execute permission fails there and falls through
# anonymously without requiring an external `test` utility.
return [IO.Path]::GetFullPath($candidate)
- Files reviewed: 23/24 changed files
- Comments generated: 0 new
- Review effort level: Lite
Remove cargo-anvil recipes, container integration, generated artifacts, and related tests from this prerequisite PR so the opt-in credential capability can be released before Anvil consumes it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
There was a problem hiding this comment.
🟡 Changes recommended
Handle invalid UTF-8 environment tokens before falling back to gh, and clarify deferred container behavior in the design documentation.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
crates/cargo-aprz/docs/DESIGN.md:117
- This paragraph documents a container-side implementation that is not part of this PR: the repository has no container credential-derivation path for this new switch, and the PR description explicitly defers container integration. As written, the design contract claims forwarding
APRZ_GITHUB_URL, token propagation, and process-tree termination already apply; remove this paragraph or clearly mark it as a future/deferred integration so the design does not promise behavior that this release does not provide.
Containerized opt-in applies the same process contract on the host because the
generated image does not contain `gh`: absolute resolution from explicit,
nonempty `PATH` entries; direct executable images only on Windows and regular
executable files on Unix; direct argument-vector launch with stdin and stderr
suppressed and strict UTF-8 stdout captured; the rejected blank environment
token removed; and a ten-second deadline that terminates the complete process
tree. Missing, unsuccessful, timed-out, blank, and invalid-output lookups all
continue anonymously. The container driver derives the same effective hostname
and forwards `APRZ_GITHUB_URL` with a discovered token so the inner provider
cannot target a different endpoint.
- Files reviewed: 11/11 changed files
- Comments generated: 1
- Review effort level: Lite
Treat non-UTF-8 GITHUB_TOKEN values as absent so an explicitly requested GitHub CLI fallback can still resolve credentials. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
There was a problem hiding this comment.
🟢 Approval recommended
No blocking issues were identified; the remaining documentation concern is a minor nit.
Review details
Suppressed comments (1)
crates/cargo-aprz/docs/DESIGN.md:119
- This section documents a containerized
ghlookup contract, but this PR deliberately contains no container or cargo-anvil integration, and the changed code provides only the host-sidecargo-aprzpath. As written, the design doc claims behavior that users of the current release cannot obtain and conflicts with the PR's deferred-adoption scope; remove this paragraph or explicitly mark it as deferred until the container integration lands.
Containerized opt-in applies the same process contract on the host because the
generated image does not contain `gh`: absolute resolution from explicit,
nonempty `PATH` entries; direct executable images only on Windows and regular
executable files on Unix; direct argument-vector launch with stdin and stderr
suppressed and strict UTF-8 stdout captured; the rejected blank environment
token removed; and a ten-second deadline that terminates the complete process
tree. Missing, unsuccessful, timed-out, blank, and invalid-output lookups all
continue anonymously. The container driver derives the same effective hostname
and forwards `APRZ_GITHUB_URL` with a discovered token so the inner provider
cannot target a different endpoint.
- Files reviewed: 11/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
Keep the controlled gh directory first while retaining the inherited PATH needed by cargo metadata and rustc on Linux CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
There was a problem hiding this comment.
🟢 Approval recommended
No blocking issues were identified; one documentation nit remains.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
crates/cargo-aprz/docs/DESIGN.md:114
- This section documents a container-side implementation (host PATH resolution, process-tree termination, and forwarding
APRZ_GITHUB_URL) that is not present in this PR; the PR explicitly defers container integration. In its current form the design document claims behavior users cannot get from this change. Move this contract to the deferred container-adoption change, or clearly label it as a future requirement rather than an implemented behavior.
- Files reviewed: 11/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
Remove the cargo-anvil container behavior from this prerequisite release design; adoption will be documented with its post-release implementation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
There was a problem hiding this comment.
🔵 Needs a closer look
Absolute PATH entries should remain usable when the current directory is unavailable.
Review details
Suppressed comments (1)
crates/cargo-aprz-lib/src/commands/github_credentials.rs:266
- When the process current directory is unavailable (for example, the directory was removed after startup), this early return prevents resolution even if
PATHcontains an absolute executable directory. That contradicts the explicit-PATH behavior documented above and makes an otherwise validghfallback silently become anonymous; only requirecurrent_dirwhen resolving relative PATH entries, while allowing absolute entries to be checked.
let current_dir = std::env::current_dir().ok()?;
- Files reviewed: 11/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
Trim explicit GitHub tokens and treat blank values as absent so later configured credential sources or anonymous access retain documented behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
🤖 Adds explicit, host-aware GitHub CLI credential discovery to cargo-aprz without invoking a developer's
ghCLI by default.--github-token-from-ghto bothcratesanddeps--github-token, nonblankGITHUB_TOKEN, explicitly enabledgh auth token --hostname <host>, then anonymous accessghonly through explicit PATH entries, accepts only direct executable images on Windows, suppresses stderr, redacts secrets, and enforces a ten-second timeout with child terminationghchild environmentThis PR intentionally contains no cargo-anvil recipes, container integration, or generated Anvil changes. Adoption is deferred until a cargo-aprz release includes this capability. Codeberg credentials are unchanged.