test: mocking harness + $HOME isolation + publish coverage (#447) - #504
test: mocking harness + $HOME isolation + publish coverage (#447)#5040xGaspar wants to merge 8 commits into
Conversation
Closes the remaining scope of #447 (PR #463 shipped the config/$HOME subset). Harness (src/test_support.rs, unit tests): - MockHttpServer: dependency-free loopback HTTP server (127.0.0.1:0, thread-per-connection). Register routes with `on(method, path, MockResponse)`, point the code under test at `server.url()`, assert on `server.requests()` (method, path, query, headers, body). `fallback` covers unregistered routes. - dead_port_url(): a closed loopback port for connection-refused paths. - GithubBaseGuard: thread-local redirection of the GitHub REST base and the git remote base, so the real publish/API call paths run offline. - GitIdentityGuard: git author/committer identity plus GIT_CONFIG_GLOBAL / GIT_CONFIG_NOSYSTEM, so commit-producing helpers never read ~/.gitconfig. Harness (tests/common/mod.rs, integration tests): - TempEnv: spawns fledge with fresh HOME/XDG_CONFIG_HOME/FLEDGE_CONFIG_DIR, non-interactive mode, every provider API key stripped, and OLLAMA_HOST pointed at a closed loopback port. Coverage: - publish.rs: the tautological tests and the empty `#[ignore] publish_live` stub are replaced by 20 real tests β get_authenticated_user, check_repo_exists (200/404/5xx), create_github_repo (body, org vs user URL, 422, 403), set_repo_topic (additive, no-duplicate, fetch failure), push_directory against a local bare repo (init+commit+push, re-push branch, no token in .git/config, git-error surfacing), run_publish orchestration (create/skip-create/abort), and resolve_owner. - github.rs: github_api_get against the mock server β headers, encoded query, 404/403/generic status mapping, non-JSON body, unreachable host, and a search payload decoded through search::parse_search_response. - llm.rs: OllamaProvider::invoke β request shape, Bearer auth, HTTP-status error text, undecodable body, connection refused, OLLAMA_HOST hint. - doctor.rs: probe_ollama_host true/false paths; tests/doctor.rs now runs under TempEnv (it previously probed whatever endpoint the developer's real config named) with two added assertions. - tests/main.rs: the two `review` cases run under TempEnv. Production seams (behavior-preserving, release builds unchanged): - github::api_base() resolves the REST base; publish::remote_base()/remote_url() resolve the push remote. Both return the previous constants unless a #[cfg(test)] thread-local override is set. No test contacts the network or reads/writes the real ~/.config/fledge. Specs updated: publish (v6), github (v4), and the publish/github/llm/doctor testing plans; CONTRIBUTING gains a "no network, no real $HOME" testing section. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
There was a problem hiding this comment.
β Corvin says...
_
<(;\ .oO(oh no...)
|/(\
\(\\
" "\\
"Caw... your imports are all over the place."
CI Summary
| Check | Status |
|---|---|
| Dependency Audit | β Passed |
| Integration (3 OS) | β skipped |
| Lint (fmt + clippy) | β Passed |
| Spec Validation | β failure |
| Tests (3 OS) | β failure |
Powered by corvid-pet
0xLeif
left a comment
There was a problem hiding this comment.
Review β REQUEST CHANGES
This is a strong test-infra PR. The harness design (dependency-free loopback MockHttpServer, thread-local GithubBaseGuard, GitIdentityGuard, TempEnv) matches fledge's blocking ureq stack well, and the production seams (github::api_base(), publish::remote_base()) are correctly #[cfg(test)]-gated so release builds stay on the production constants. Specs/testing plans were updated honestly. macOS + Ubuntu tests pass.
Blocker 1: Windows unit test failure (real bug)
test (windows-latest) fails on a single test:
publish::tests::push_directory_initializes_commits_and_pushes
assertion failed: config.contains(remotes.path().to_str().unwrap())
at src/publish.rs:786
988 passed / 1 failed. The assertion that .git/config contains the raw Windows path from Path::to_str() is brittle: git on Windows typically normalizes remotes to forward slashes (and sometimes a different drive prefix form). The push itself succeeded (the bare-repo log / ls-tree asserts above it would have failed otherwise); only the path-string check is wrong.
Suggested fix: assert via git remote get-url origin (or normalize both sides to / / PathBuf compare) instead of substring-matching the OS path in the config file text. Apply the same care anywhere else you assert path-in-config.
Blocker 2: SpecSync β no active change covering the production paths
spec-check / trust fail with:
meaningful changed paths are not covered by an active change:
src/doctor.rs, src/github.rs, src/llm.rs, src/publish.rs,
src/test_support.rs, tests/common/mod.rs, tests/doctor.rs, tests/main.rs
Plus stale accepted-change errors from editing CHANGELOG.md and specs/doctor/testing.md after those inputs were locked by CHG-0001 / CHG-0004 / CHG-0006.
Please add an active SpecSync change for this work (or attach it to one), and reopen/succeed the staled accepted changes per the specsync change reopen β¦ guidance in the log.
Non-blocking notes (optional follow-ups)
- Hand-rolled HTTP mock is the right call over wiremock/tokio here; please keep
MockHttpServerdocumented in CONTRIBUTING (already done β good). GithubBaseGuard::apisnapshots the current remote override but does not clear it; that is fine with thread-locals + RAII as long as every test that sets remote usesapi_and_remote(or a dedicated remote-only guard). Worth a one-line comment if you touch the file again.
Happy to re-review once Windows is green and the SpecSync contract passes.
β¦d-publish-coverage
`push_directory_initializes_commits_and_pushes` substring-matched the raw `Path::to_str()` of the temp remote directory against the text of `.git/config`. That is not portable: git's config writer escapes `\` as `\\`, so on Windows the file never literally contains the path handed to git (and git may also normalise separators in a remote URL). The push itself was fine β only the path-string check was wrong. Read the remote back through `git remote get-url origin` instead and compare separator-insensitively. Also assert the token is absent from the origin URL, not just from the config text. `git_out` now takes the `.git` directory (unchanged for the bare-repo call sites, where the repo path *is* the git dir). Documents why `GithubBaseGuard::api` may leave the remote-base thread-local untouched: it is only ever written by `api_and_remote`, whose guard restores it on drop, so an `api`-only test cannot observe a leaked override.
There was a problem hiding this comment.
β Corvin says...
_
<(;\ .oO(oh no...)
|/(\
\(\\
" "\\
"I'm pecking through the errors..."
CI Summary
| Check | Status |
|---|---|
| Dependency Audit | β Passed |
| Integration (3 OS) | β Passed |
| Lint (fmt + clippy) | β Passed |
| Spec Validation | β failure |
| Tests (3 OS) | β Passed |
Powered by corvid-pet
Addresses the review blocker on #504: the source and spec paths touched by this PR had no active SpecSync change, so trust/spec-check failed. Recorded through the normal lifecycle (draft, interview, definition approval, implementation, verify-native, closing approval) with semantic deltas for the four affected modules -- github, publish, llm, doctor -- and requirement evidence for REQ-github-020, REQ-publish-020, REQ-llm-020 and REQ-doctor-020. Also documents `api_base` in the github spec's Public API, which the export-coverage gate correctly flagged as undocumented once the test-only base-URL seam was introduced. Allocated as CHG-0008 rather than CHG-0007: #503 already claims 0007 on its branch, and the sequence ledger on main is still at 6, so the tool would otherwise hand both PRs the same identity. Verification passed (1 command, 4 requirements); `specsync check` exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy
There was a problem hiding this comment.
β Corvin says...
_
<(^\ .oO(Caw! ^v^)
|/(\
\(\\
" "\\
"That's a nice looking export you've got there."
CI Summary
| Check | Status |
|---|---|
| Dependency Audit | β Passed |
| Integration (3 OS) | β Passed |
| Lint (fmt + clippy) | β Passed |
| Spec Validation | β Passed |
| Tests (3 OS) | β Passed |
Powered by corvid-pet
0xLeif
left a comment
There was a problem hiding this comment.
Approved. The test harness and isolation changes are now covered across CI, including Windows; CI and trust are green.
Addresses the review blocker on #505: CHG-0007 existed but sat in draft, and draft does not count as an active change for path coverage, so the new lint surface was uncovered for the trust gate. Renumbered CHG-0007 -> CHG-0009. #503 already claims 0007 and #504 claims 0008 on their branches while the sequence ledger on main is still at 6, so the tool would otherwise hand several open PRs the same identity. The rename was done while the record was still draft, before any approval digest covered its id. Taken through the full lifecycle: semantic deltas for the two affected modules (spec, main), requirement evidence for REQ-spec-030/031/032 and REQ-main-010, definition approval, verify-native, closing approval. Also documents three lint exports the coverage gate flagged -- ACCEPTANCE_SECTION and REJECTION_SECTION were sharing one table row, and Finding::error was missing entirely. The two open maintainer decisions (lane wiring, layer-2 default) moved out of the task list into a deferred-decisions section: they are questions for the reviewer, not work items of this change, and both ship with the conservative default. Verification passed (1 command, 4 requirements); `specsync check` exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy
β¦d-publish-coverage # Conflicts: # .specsync/change-sequence.json
Merging main (which landed CHG-0007 via #503) advanced `.specsync/change-sequence.json`, which is an exact-match delivery input in every acceptance manifest. That staled both accepted records even though no real delivery input changed -- the cascade documented in CorvidLabs/spec-sync#481. Both were healed through the supported lifecycle rather than by hand: reopen -> verify -> accept, with the verify-native lane passing for each (CHG-0008 re-evidencing all four requirements). No approval digests were rewritten. Note `reopen` succeeded here where it deadlocked on the pre-existing records fixed in #506, because these two carry consistent closing-approval digests from their original acceptance. `specsync check` exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy
ce978f4
There was a problem hiding this comment.
β Corvin says...
_
<(^\ .oO(Caw! ^v^)
|/(\
\(\\
" "\\
"Caw! Found a shiny new spec!"
CI Summary
| Check | Status |
|---|---|
| Dependency Audit | β Passed |
| Integration (3 OS) | β Passed |
| Lint (fmt + clippy) | β Passed |
| Spec Validation | β Passed |
| Tests (3 OS) | β Passed |
Powered by corvid-pet
Records the #507 work through the normal lifecycle: interview, semantic delta for the run module, requirement evidence for REQ-run-020/021/022, definition approval, verify-native, closing approval. Allocated as CHG-0010 rather than the CHG-0008 the tool offered: #504 already claims 0008 and #505 claims 0009 on their branches while the sequence ledger on main is at 7, so the tool hands every open PR a colliding identity. Renumbered while still draft, before any approval digest covered the id. Also re-verified CHG-0007, which the sequence-ledger bump staled even though no real delivery input changed (CorvidLabs/spec-sync#481). Healed via reopen -> verify -> accept; no approval digests were rewritten. `specsync check` exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy
0xLeif
left a comment
There was a problem hiding this comment.
Review β REQUEST CHANGES
Automated review via Claude Code (/code-review), covering this PR alongside #509 and #505.
CI is green on the current commit, but my earlier approval was on a prior commit that got superseded by a later push, so this HEAD hasn't had a human/AI review yet. Re-reviewing surfaced that the isolation harness doesn't fully deliver on its own stated guarantees, plus two plausible flakiness/leak issues under parallel test execution.
Blocker 1: TempEnv's isolation doc oversells itself β GitHub calls from the spawned subprocess aren't actually mocked
tests/common/mod.rs:45 β the doc comment claims a wrapped command can "neither read nor write ~/.config/fledge/, nor reach any network endpoint," but GithubBaseGuard's override is #[cfg(test)]-only in the library crate and never reaches the real fledge binary that TempEnv spawns as a subprocess via cargo_bin().
Impact: any TempEnv-wrapped integration test that runs a GitHub-touching command (fledge templates search, fledge plugins search, fledge github ...) makes a real unauthenticated HTTPS request to api.github.com β flaky/rate-limited in CI and a genuine network egress despite the stated hermetic design.
Blocker 2: remote template git clone bypasses the mock harness entirely
src/test_support.rs:211 β the mocking-harness module doc claims "remote template fetch" is covered by the ureq-based mock, but src/remote.rs::clone_repo shells out to a real git clone subprocess against a hardcoded https://github.com/... URL that's never routed through GithubBaseGuard/remote_base().
Impact: a test exercising fledge templates init <owner>/<repo> for a remote template still performs a real git clone against github.com no matter how it's wrapped.
Blocker 3: two pre-existing doctor e2e tests weren't migrated to TempEnv
tests/main.rs:231 β e2e_rust_project_lifecycle and e2e_tsbun_project_lifecycle (which invoke doctor/doctor --json) were left on the old unisolated run_fledge_in helper instead of being retrofitted, unlike sibling tests this same PR did convert.
Impact: these tests still read the developer's real ~/.config/fledge/config.toml; doctor's check_ai() calls probe_ollama_host against whatever host is configured there, so on a machine/CI runner with a real provider key set, cargo test triggers a live network probe β the exact class of leak issue #447 (which this PR claims to close) remains latent here.
Plausible, worth a look
- TOCTOU race in
dead_port_url()/closed_loopback_addr()(src/test_support.rs:491) β both synthesize a "known-closed" port via bind-then-drop, which races undercargo test's default parallel execution: between the drop and the caller's connect attempt, the OS can hand the freed port to a different concurrently-running test's ownbind(0)call. Could cause intermittent CI flakes. MockHttpServerleaks per-connection handler threads (src/test_support.rs:334) β the accept loop spawns an untracked thread per connection;Droponly joins the top-level accept-loop thread. A blocked handler (bad Content-Length, slow/partial write) stays running detached after the test drops the server.
Non-blocking note
api_base()/remote_base() (src/github.rs:17, duplicated in src/publish.rs) embed the #[cfg(test)] thread-local override lookup inline rather than as an injected parameter, and the pattern is independently duplicated across the two files β nothing at the type level stops a test from forgetting the guard and silently hitting the real endpoint.
Verdict
The harness design itself (dependency-free MockHttpServer, GithubBaseGuard, GitIdentityGuard, TempEnv) is the right shape and Windows is green now β good progress since the last round. But three real gaps mean the isolation guarantee this PR sets out to deliver isn't fully true yet for GitHub-touching and legacy-doctor test paths. Please close those before merge.
β¦aries Review blocker 1: `TempEnv`'s doc claimed a wrapped command could not "reach any network endpoint", but `GithubBaseGuard` is a `#[cfg(test)]` thread-local that cannot cross into the `fledge` binary `TempEnv` spawns. Any GitHub-touching command under it would have gone to api.github.com for real. Give the binary a runtime hook instead of narrowing the claim: `github::api_base()` and a new `github::remote_base()`/`remote_url()` accept `FLEDGE_TEST_GITHUB_API_BASE` / `FLEDGE_TEST_GITHUB_REMOTE_BASE`. It is a test hook, not configuration, and is doubly gated β compiled out of release builds (every shipped binary), and in debug builds honoured only for a loopback `http://` host or an existing local directory, with userinfo forms such as `http://127.0.0.1@evil.example` rejected. So no environment can redirect an `Authorization: Bearer <token>` request off the machine; rejected values fall back to the production constant with a warning. `TempEnv` points both at a closed loopback port by default, so a stray GitHub call now fails locally. Review blocker 2: remote template fetch shells out to `git clone`, which no HTTP mock can intercept. `remote::clone_repo` now builds its URL through `github::remote_url`, so a test can clone from a local bare repo β the real clone path, offline. `publish` drops its duplicate `remote_base` and shares the same helper. Also isolate the four `doctor` invocations in tests/main.rs that were still using the unwrapped helpers: they read the developer's real config and probed whatever AI host it names, the last live-network path left in the suite. New tests: `tests/isolation.rs` (mock reached through the spawned binary, default dead-port behaviour, remote-template clone from a local bare repo) and unit tests in `src/github.rs` covering unset/loopback/hostile values and the release-build fallback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy
There was a problem hiding this comment.
β Corvin says...
_
<(;\ .oO(oh no...)
|/(\
\(\\
" "\\
"I'm pecking through the errors..."
CI Summary
| Check | Status |
|---|---|
| Dependency Audit | β Passed |
| Integration (3 OS) | β failure |
| Lint (fmt + clippy) | β Passed |
| Spec Validation | β failure |
| Tests (3 OS) | β Passed |
Powered by corvid-pet
The review fixes changed src/ under an already-accepted record, staling
CHG-0008. Healed via reopen -> verify -> accept (verify-native green,
4 requirements re-evidenced).
The fixes also introduced src/remote.rs and tests/isolation.rs, which
CHG-0008 did not cover. spec-sync refuses to widen the definition of an
already-applied change ("perform further spec changes in a new change
workspace"), so those land as CHG-0011 with its own remote delta and
REQ-remote-010 rather than by editing the accepted definition.
Allocated 0011 because 0008 (this PR and #511), 0009 (#505) and 0010
(#509) are all claimed on open branches.
Also documents four github exports the coverage gate flagged: remote_base
and remote_url were sharing one table row, and API_BASE_ENV /
REMOTE_BASE_ENV were undocumented.
`specsync check` exits 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy
0xLeif
left a comment
There was a problem hiding this comment.
Request changes β two small fixes, one of which is red on head right now
Everything the 2026-08-13 review asked for is done, and I verified each rather than trusting the commit message: the env hook now crosses the process boundary with an honest "what it does not guarantee" doc section; src/remote.rs:78 builds its URL via crate::github::remote_url and tests/isolation.rs:117-152 clones through the unmodified production path (I ran it β passes); the two doctor e2e tests were migrated plus three more sites. Locally on head: 1005 unit tests pass, tests/isolation.rs and tests/doctor.rs pass.
The threat model on the env hook is unusually well reasoned. Two independent gates (#[cfg(debug_assertions)] with a None-returning release stub, and no custom [profile.release] in Cargo.toml), and is_loopback_http_url correctly rejects the http://127.0.0.1@evil.example userinfo trick by refusing any authority containing @. The hostile-value test table walks six forms including suffix-confusion and scheme-relative. Replacing tautological tests like topics_include_fledge_template β which asserted a copy of the production logic β with real ones is the right trade.
π΄ Blocking: Windows clippy is red on this PR's own new file
tests/isolation.rs:17-18:
use std::path::Path;
use std::process::Command;Both are unconditional, but every consumer is #[cfg(not(windows))]. cargo clippy --all-targets -- -D warnings promotes unused_imports to an error, so the test target fails to compile:
error: could not compile fledge (test "isolation") due to 2 previous errors
Lane 'check' failed at step 1
Confirmed on run 32036644470, job 95408935980, at headSha 0be2268. Introduced by f8fd97a β the very commit that fixed Blocker 1.
It's invisible to the required checks because ci.yml's lint job runs cargo clippy without --all-targets, on ubuntu only. Note test (windows-latest) is SUCCESS β that's cargo test, which doesn't run clippy, so "Windows tests pass" and "Windows lint is broken" are both true. Merging as-is lands a permanently red Windows job on main.
Fix: gate both imports with #[cfg(not(windows))].
π΄ Blocking: the new env override makes two pre-existing tests racy
build_api_url used to interpolate a literal; it now reads process-global FLEDGE_TEST_GITHUB_API_BASE via api_base(). But build_api_url_without_query and build_api_url_encodes_and_joins_query still hard-code https://api.github.com and do not take env_lock(), while endpoint_env_override_redirects_only_in_debug_builds sets that var under the lock.
Reproduced independently on a fresh worktree: 60 runs at --test-threads 16 β one failure with both tests failing together (left: "http://127.0.0.1:9/repos/CorvidLabs/fledge"), and a plain cargo test github at default parallelism β 4 failures in 40 runs, ~10%. Full-suite runs at default and --test-threads 4 were clean, which is why CI is green β the window is narrow, not absent.
This PR's own module doc states the rule being broken (src/test_support.rs:56-63), and CONTRIBUTING.md now says "Env-mutating tests must hold env_lock()". The gap is that build_api_url became an env reader without being brought under the lock. One line per test: let _lock = env_lock(); plus EnvVarGuard::set(API_BASE_ENV, None).
Minor
- CONTRIBUTING.md:140 now states "No test may touch the network or your real
~/.config/fledge/", which ~10 integration sites still violate β all fivetests/config.rstests,plugin listΓ2,templates listΓ2,ai status --json.plugin listis worse than described:src/plugin/mod.rs:374-390resolves fromdirs::config_dir()and doesn't honourFLEDGE_CONFIG_DIRat all, socli_plugin_list_emptyreads the developer's realplugins.tomland, asserting onlystatus.success(), provides no coverage of the "empty" case it names. No test writes the real config β I checked. Read-only leak, developer-machine only, zero CI impact. dead_port_url()bind-then-drop is a TOCTOU; raised on 08-13 and untouched. I didn't hit it in ~70 runs, but it's load-bearing fordefault_temp_env_points_github_at_a_dead_port, whose entire assertion is that the connection fails.- The
MockHttpServerthread-leak fix landed only in the integration-test copy; the two servers have now diverged with no note saying why. TestRepo::init()doesn't neuter the global gitconfig, unlike every other git helper here β a developer withcommit.gpgsign = trueor a globalcore.hooksPathgets failures or hooks running under test.- PR body says specs went
publishv5βv6 andgithubv3βv4; head is actually v7 and v5, andremotev3βv4 isn't mentioned.
Both blockers are a handful of lines. This should go green quickly and I'll re-review straight away.
π€ Reviewed via Claude Code
Addresses the remaining scope of #447 β the mocking harness and publish-flow isolation. (The
$HOME-isolation subset landed earlier in #463, which auto-closed the issue prematurely; this does not redo that work.)The harness
Unit tests β
src/test_support.rsMockHttpServer::start()β loopback server on127.0.0.1:0, thread-per-connection, shuts down on drop.server.on("GET", "/user", MockResponse::json(200, β¦)), then assert againstserver.requests()βRecordedRequest { method, path, query, headers, body }.dead_port_url()β closed loopback port for connection-refused paths.GithubBaseGuard::api(url)/::api_and_remote(...)β thread-local redirection of the GitHub REST base and git remote base, so parallel tests can't see each other's redirection.GitIdentityGuardβ git identity plusGIT_CONFIG_GLOBAL/GIT_CONFIG_NOSYSTEM, so commit-producing code never reads~/.gitconfig.Integration tests β
tests/common/mod.rs:TempEnvspawnsfledgewith freshHOME/XDG_CONFIG_HOME/FLEDGE_CONFIG_DIR,FLEDGE_NON_INTERACTIVE=1, all ten provider API keys plusGITHUB_TOKEN/GH_TOKENremoved, andOLLAMA_HOSTpointed at a closed loopback port.Note on
wiremock: deliberately not used. It would pull in tokio, while all fledge HTTP is blockingureq. The hand-rolled server adds no dependencies.Coverage added
src/publish.rs(the explicit ask) β the tautological tests and the empty#[ignore] publish_livestub are gone, replaced by ~20 real tests:get_authenticated_user,check_repo_exists200/404/5xx,create_github_repo(body fields, user vs org endpoint, 422, 403),set_repo_topic(additive merge, no duplicate, fetch failure),push_directoryagainst a local bare repo (init+commit+push, re-push, no token leaked into.git/config, git-stderr surfacing), andrun_publishorchestration.src/github.rsβgithub_api_getend to end: headers, percent-encoded query, 404/403/generic status mapping, non-JSON body, unreachable host.src/llm.rsβOllamaProvider::invoke: request shape, Bearer auth vs none, HTTP-status errors, undecodable body, connection refused, and theOLLAMA_HOSThint (OLLAMA_HOSTenv var overrides config without clear feedback in error messagesΒ #378).src/doctor.rs+tests/doctor.rsβ the CLI doctor tests previously probed whatever endpoint the developer's real config named; they now run underTempEnv.Production seams introduced
Behavior-preserving; release builds are byte-identical.
github::api_base()β was an inlinehttps://api.github.comliteral, now apub(crate)fn with a#[cfg(test)]-only thread-local override.publish::remote_base()/remote_url(owner, repo)β same treatment for the inline remote URL. This is what makesrun_publishtestable end to end against a local bare repo.No network, no real
$HOMEEvery new HTTP call targets
127.0.0.1(mock server or closed port); git operations use tempdir working trees and local bare remotes withGIT_CONFIG_GLOBALneutered; config access goes throughFLEDGE_CONFIG_DIR/HOMEtempdirs. This rests on a full diff audit rather than a network namespace, which the sandbox blocked.Verification
cargo fmt --checkandcargo clippy --all-targets -- -D warningsclean;cargo testpasses (999 unit + all integration binaries).Specs updated:
publishv5βv6 andgithubv3βv4 (new invariants for the base-URL indirection), plus thepublish/github/llm/doctortesting plans β the publish plan previously listed tests that did not exist.fledge spec checkis red onmainfor unrelated reasons (six stale SDD change-record errors β CorvidLabs/spec-sync#481); this PR neither adds to nor fixes that.π€ Generated with Claude Code
https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy