Skip to content

An undecodable DEVLAUNCH_AID_AGENT is a name to refuse, not an unset variable - #413

Merged
blooop merged 4 commits into
mainfrom
fix/aid-agent-env
Aug 25, 2026
Merged

An undecodable DEVLAUNCH_AID_AGENT is a name to refuse, not an unset variable#413
blooop merged 4 commits into
mainfrom
fix/aid-agent-env

Conversation

@blooop

@blooop blooop commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Closes the DEVLAUNCH_AID_AGENT finding from the 2026-08-24 architecture review.

The bug

aid/src/main.rs read the variable with

std::env::var(rewrite::AGENT_ENV_VAR).ok().as_deref()

and osext's own module doc names why that is wrong: std::env::var(..).ok()
reports a value that is not valid UTF-8 as unset. So
DEVLAUNCH_AID_AGENT=$'\xff' was not a broken agent name, it was no agent name —
rewrite::default_agent saw None, handed back claude, and aid opened the
workspace and started the wrong agent. The UnknownAgentInEnvironment refusal
that DEVLAUNCH_AID_AGENT=nope already gets was unreachable for exactly the
values that cannot be written as a string.

Same class as the DEVLAUNCH_NO_TTY divergence fixed in b52f4fa, milder
consequence: a wrong agent rather than an inverted opt-out.

The constraint that shaped the fix

aid depends on dl and on no other devlaunch crate, deliberately — the rule is
stated in aid/Cargo.toml and in the module doc at the top of aid/src/main.rs,
and Python pinned it as an AST test. So aid cannot reach osext, and it cannot
spell the reading correctly for itself either: the lossy decode is the fix. A
copy of it inside aid would be a second reading of one boundary, which is the
thing b52f4fa had just finished deleting.

So the reader is promoted and re-exported, following the path shell and
python_repr already take (dl/src/lib.rs):

  • devlaunch_core::osext becomes pub, with env_str as its one public item.
    strip, home_dir and temp_dir were pub inside a pub(crate) mod and are
    now pub(crate), so the module going public promotes exactly the one function
    that has a caller outside the crate — the same shape json already has, which
    is pub for JsonKind alone.
  • dl re-exports env_str.
  • aid calls dl::env_str(rewrite::AGENT_ENV_VAR).

aid's dependency story is unchanged: the whole of devlaunch it can see is still
dl's entry point and the three items dl hands it.

What this deliberately does not decide. #406 parks "should osext become
binary surface wholesale" until a second caller asks for it. This promotes one
reader, not the module.

Red, then green

The test spawns the binary with the raw byte in its environment, because the
boundary is where the bug lives — the predicate underneath was always correct, so
a unit test of default_agent is green before the change and proves nothing.
aid_with is keyed by OsStr values now for the same reason: the value that
reproduces this cannot be written as a &str.

Before the change, cargo test -p aid --test rewrite an_agent_name_that_does_not_decode:

thread 'an_agent_name_that_does_not_decode_is_refused_rather_than_read_as_unset' panicked at aid/tests/rewrite.rs:151:9:
assertion `left == right` failed: expected exit 1; stdout: ; stderr: aid -> dl devlaunch-main-zovomobo -- 'CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1 IS_SANDBOX=1 claude --dangerously-skip-permissions hi'
Workspace devlaunch-main-zovomobo is already running, attaching...
SSH command: devpod ssh devlaunch-main-zovomobo --command bash -lc '... claude --dangerously-skip-permissions hi'

  left: Some(0)
 right: Some(1)

That is the bug itself in the failure output: exit 0, the default agent started,
the workspace attached. After the change the same test passes, and the run ends

DEVLAUNCH_AID_AGENT='\u{fffd}' is not a known agent. Choose one of: claude, codex, gemini.

with no devpod call made. The name is U+FFFD because the reading is a lossy
decode: the undecodable byte is present under the replacement character, which
is what makes the value a name to refuse rather than a variable to ignore.

Gate

  • cargo test --workspace — green (2 full runs; one intermediate run hit
    dl --test picker::what_is_typed_... on a pty echo race, "> wayfind" vs
    "> wayfinder". It passes alone 3/3 and the whole suite passes on the very
    next run of this same code; same family as The aid pty tests flake on a loaded cargo test --workspace run #401, unrelated to this diff, which
    touches nothing the picker reads.)
  • cargo clippy --locked --all-targets -- -D warnings — clean
  • cargo fmt --check — clean
  • public_api_snapshots.rs — green. cargo-public-api needs nightly and cannot
    be installed here, so public-api.rest.txt gains its two rows by hand and
    CI's regeneration is authoritative if it disagrees.

Refs #402, map #406.

Summary by Sourcery

Refuse undecodable DEVLAUNCH_AID_AGENT values instead of treating them as unset and starting the default agent.

Bug Fixes:

  • Prevent aid from launching the default agent when DEVLAUNCH_AID_AGENT contains undecodable bytes by refusing the value as an unknown agent.

Enhancements:

  • Expose the shared environment-variable reader through dl so entry points consistently preserve present-but-non-UTF-8 values.

Tests:

  • Add an integration test covering undecodable agent names and verifying that no devpod call is made.

…variable

`aid` read the variable with `std::env::var(..).ok()`, which reports a value that
is not valid UTF-8 as *unset*. So `DEVLAUNCH_AID_AGENT=$'\xff'` did not name a
broken agent, it named no agent at all: `default_agent` saw `None`, returned the
default, and aid opened the workspace and started claude. The refusal
`UnknownAgentInEnvironment` exists to give -- the one `DEVLAUNCH_AID_AGENT=nope`
already got -- was unreachable for the only values that cannot be typed as a
string. Same class as the `DEVLAUNCH_NO_TTY` divergence, milder consequence: a
wrong agent rather than an inverted opt-out.

The fix could not be local, which is why the finding was filed rather than fixed
alongside that one. `aid` depends on `dl` and on nothing else of devlaunch, on
purpose (`aid/Cargo.toml`, and the module doc at the top of `aid/src/main.rs`),
so it cannot reach `osext` and cannot spell the reading correctly for itself
either: the lossy decode is the whole of what makes it right. A copy of it in
`aid` would be the second reading of one boundary that b52f4fa had just finished
deleting.

So `osext::env_str` becomes binary surface and `dl` re-exports it beside `shell`
and `python_repr`, which is the door `aid` already reaches core's quoting
through. `aid`'s dependency story is unchanged: the whole of devlaunch it can see
is still dl's entry point and the three things dl hands it.

One reader promoted, not the module opened. `strip`, `home_dir` and `temp_dir`
were `pub` inside a `pub(crate) mod` and are now `pub(crate)`, so making the
module public promotes exactly the one function with a caller outside the crate
-- the same shape `json` already has, which is `pub` for `JsonKind` alone.
Whether the rest of `osext` should follow is #406's open question, and it wants a
second caller before it is answered rather than this one.

The regression test spawns the binary with the byte in the environment, because
that is the only place the bug lives: the predicate underneath was always
correct, and a unit test of it is green before the change. `aid_with` is keyed by
`OsStr` values now for the same reason -- the value that reproduces this is not a
string.

`public-api.rest.txt` gains the two rows by hand; CI's regeneration is
authoritative if it disagrees.

Refs #402, map #406.

@sourcery-ai sourcery-ai 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.

Sorry @blooop, you have reached your weekly rate limit of 250000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Reviewer's Guide

Routes aid's agent environment lookup through the shared lossy OS-environment reader exposed by devlaunch-core and re-exported by dl, ensuring present-but-undecodable values are refused as unknown agents rather than treated as unset and defaulting to claude. Adds a raw-byte integration regression test while preserving aid's dependency boundary and narrowing the newly public core surface to env_str.

Sequence diagram for refusing an undecodable agent name

sequenceDiagram
    participant Env as OS environment
    participant Aid as aid
    participant DL as dl
    participant Core as devlaunch_core::osext
    participant Rewrite as rewrite

    Env->>Aid: DEVLAUNCH_AID_AGENT=raw invalid UTF-8 bytes
    Aid->>DL: env_str(AGENT_ENV_VAR)
    DL->>Core: env_str(name)
    Core-->>DL: Some(String with U+FFFD)
    DL-->>Aid: Some(agent_name)
    Aid->>Rewrite: parse_aid_args(argv, agent_name)
    Rewrite-->>Aid: UnknownAgentInEnvironment
    Aid-->>Env: Refusal - no workspace or agent launch
Loading

File-Level Changes

Change Details Files
Preserve undecodable environment values as present when selecting the aid agent.
  • Replace lossy std::env::var(...).ok() handling with the shared dl::env_str reader.
  • Ensure undecodable bytes become U+FFFD and flow through normal unknown-agent refusal instead of selecting the default agent.
rust/aid/src/main.rs
rust/devlaunch-core/src/osext.rs
rust/dl/src/lib.rs
Expose the shared environment reader through the existing dependency boundary without broadening the aid dependency graph.
  • Make the osext module public while keeping strip, home_dir, and temp_dir crate-private.
  • Re-export only env_str from dl for aid and update the public API snapshot.
rust/devlaunch-core/src/lib.rs
rust/devlaunch-core/src/osext.rs
rust/dl/src/lib.rs
rust/devlaunch-core/public-api.rest.txt
Add an integration regression test that exercises the raw operating-system environment boundary.
  • Allow test fixtures to set environment values as OsStr and Unix bytes.
  • Verify an undecodable agent name exits with the replacement-character refusal and makes no devpod calls.
  • Update the existing valid-string environment fixture to use the new representation.
rust/aid/tests/rewrite.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

blooop added a commit that referenced this pull request Aug 24, 2026
#413 makes osext::env_str reachable from the binaries, which retires the reason
this doc gave for existing. The function still earns its keep, for a different
reason worth writing down: dl asks for the decision, not the value, and
composing it on dl's side would export tty_disabled and DISABLE_VAR to say what
one item says -- putting the composition back on the side of the wall that got
it wrong the first time.
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.20%. Comparing base (c437fc7) to head (0c06c74).

Additional details and impacted files
Flag Coverage Δ
python 42.98% <ø> (ø)
rust 95.56% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
shipped code (rust) 95.56% <100.00%> (-0.01%) ⬇️
harness and tooling (python) 42.98% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

blooop added a commit that referenced this pull request Aug 24, 2026
#413 makes osext::env_str reachable from the binaries, which retires the reason
this doc gave for existing. The function still earns its keep, for a different
reason worth writing down: dl asks for the decision, not the value, and
composing it on dl's side would export tty_disabled and DISABLE_VAR to say what
one item says -- putting the composition back on the side of the wall that got
it wrong the first time.

@blooop blooop left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This was generated by AI during review.

Reviewed at merge-base 57955a3...136e00b. Everything below was run in a scratch worktree; cargo output is pasted where a claim rests on it.

Preflight: ci, rust, e2e, prek, public-api, packaging, coverage all green. review/gate are red only because no wf-review report exists yet — the job's own message says so ("The external reviewer refused ... and there is no self-review to stand in for it. Post a wf-review report on this pull request and re-run this job"). Not a code failure.

Standards

1. CONFIRMED — the module this PR makes public has three dead intra-doc links. osext's module doc links [strip], [home_dir] and [temp_dir]; all three are now pub(crate), so rustdoc drops the links and warns. This PR's own public-api job says it (18:00:30):

warning: public documentation for `osext` links to private item `strip`
  --> devlaunch-core/src/osext.rs:14:9
warning: public documentation for `osext` links to private item `home_dir`
  --> devlaunch-core/src/osext.rs:18:9
warning: public documentation for `osext` links to private item `temp_dir`
  --> devlaunch-core/src/osext.rs:22:9

The same job on #414 and #415 emits none of them, so these are this diff's. Reproduced locally with cargo doc -p devlaunch-core --no-deps. Three of that doc's four bullets now render as plain text on the one module the PR promotes, and the doc is the first thing an external reader of devlaunch_core::osext lands on. timing already ships this warning shape, so it is not a new class — but it is a new instance, and a one-word edit each (drop the brackets, or link them as crate::osext::strip with --document-private-items off). Not blocking; worth the commit.

2. Design alternative, not blocking. The stated goal — "one reader promoted rather than the module opened" — is reachable more literally without opening the module: keep pub(crate) mod osext; and add pub use osext::env_str; at core's root. That exports exactly one item (one snapshot row, not two), leaves the module doc private so finding 1 disappears, and dl re-exports devlaunch_core::env_str unchanged from aid's side. The cost is losing osext:: from the path in the snapshot, which is a real readability loss — so this is a judgement call rather than a defect. Recording it because the PR body argues the narrow shape and this is the narrower one.

Checked and clean:

  • The two hand-edited snapshot rows are right, and not on my reading of them. CI's public-api job regenerates all three snapshots with the pinned cargo-public-api 0.52.0 into a scratch tree and diff -us them against the repo (scripts/public-api-snapshots.sh, ci.yml:434-443). It passed on 136e00b having diffed three files, so the rows are byte-identical to a real regeneration. For what it is worth independently: sort position is right (notices < osext < runner), and pub mod + one signature row matches json's convention at public-api.rest.txt:2258.
  • The demotion is not a surface break. strip/home_dir/temp_dir were pub inside a pub(crate) mod, so unreachable outside the crate by construction. grep -rn "osext" rust/ --include=*.rs outside devlaunch-core/src/ returns nothing, and every caller is a crate::osext:: path inside core.
  • aid's dependency story is intact. aid/Cargo.toml still names dl as its only devlaunch dependency and grep -rn "devlaunch_core\|devlaunch-core" rust/aid/ returns nothing. dl::env_str reaches core's reading the same way shell and python_repr do.
  • Nit, pre-existing: the crate doc still describes the binary surface as "the four layer modules" while json, shell, timing, notices and now osext are also pub. Already drifted before this PR; one more instance.

Spec

Against #402.

  • "a DEVLAUNCH_AID_AGENT holding undecodable bytes silently selects the default agent instead of refusing with UnknownAgentInEnvironment" — fixed, and the test is genuinely red on the old code rather than green-either-way. Reverted rust/aid/src/main.rs alone to 57955a3 on top of this branch and ran the new test:
thread 'an_agent_name_that_does_not_decode_is_refused_rather_than_read_as_unset' panicked at aid/tests/rewrite.rs:151:9:
assertion `left == right` failed: expected exit 1; stdout: ; stderr: aid -> dl devlaunch-main-zovomobo -- '... claude --dangerously-skip-permissions hi'
Workspace devlaunch-main-zovomobo is already running, attaching...
  left: Some(0)
 right: Some(1)

That is the bug, from a spawned binary carrying a real OsStr::from_bytes(b"\xff") in its environment — Command::env takes AsRef<OsStr>, so the raw byte reaches the child unmangled and the non-UTF-8 path is the one under test. Unmodified branch: 12/12 green.

  • "Either re-export the reader through dl ... or have rewrite take the already-decoded value" — the first option, which the ticket offers by name.
  • "DEVLAUNCH_AID_AGENT=$'\xff' should refuse by name, not fall through to the default" — met; the refusal names the value as '\u{fffd}', and world.devpod_calls().is_empty() pins that nothing opened.
  • "the general fix is to make osext binary surface" — deliberately deferred to #406 and said so. Consistent with the ticket, which files it as a "while there" rather than a requirement.

Nothing unverifiable on this one.

Verdict

Approve. No blocking findings. One confirmed defect worth a follow-up commit before merge: the three rustdoc::private_intra_doc_links warnings introduced in the newly-public osext module doc.

Making `osext` public turned its module doc into public documentation, and three
of its four bullets link items that stayed `pub(crate)`. rustdoc drops such a
link, renders the name as plain text, and warns:

    warning: public documentation for `osext` links to private item `strip`
      --> devlaunch-core/src/osext.rs:14:9

with `home_dir` and `temp_dir` behind it. Three warnings this diff introduced,
on the one module it promotes, in the first paragraphs an external reader of
`devlaunch_core::osext` lands on. The brackets are off now and the prose is
otherwise untouched, so the bullets read exactly as they did -- they just no
longer promise a link that was never going to render. A short note above them
says why only `env_str` is linked, so the next reader restores the brackets on
purpose or not at all.

The review offered a second shape: keep `pub(crate) mod osext` and put
`pub use osext::env_str;` at core's root, which exports one item instead of a
module and makes the module doc private again, dissolving all three at once.
Not taken, for three reasons.

The public path is the smallest of them: `devlaunch_core::env_str` loses the
`osext::` that says which boundary the reader belongs to, and the snapshot row
is where anyone reviewing the surface reads it.

The second is that the dead-link shape is the crate's, not this module's.
`cargo doc -p devlaunch-core --no-deps` emits 112 of these warnings; `timing`
ships five, `clients::devpod` eight, `clients::gh` more. Collapsing `osext`
would take three of 112 and leave the pattern standing everywhere else, so it
buys tidiness in one module rather than a class of warning gone.

The third is that `pub mod osext` is the seam #406 asks its question at --
whether the rest of the module should follow `env_str` out. A root re-export
answers "no" by construction and would have to be unpicked to answer "yes".

Cost against benefit: the alternative also rewrites `public-api.rest.txt` for a
shape `cargo-public-api` cannot be run against in this container (it needs
nightly), so the snapshot edit would be a guess pushed to CI to adjudicate.
That is a lot of motion for a cosmetic finding on an approved branch, and this
edit is four lines that need no snapshot at all.

`cargo doc -p devlaunch-core --no-deps` now names `osext` in no warning.
cargo test --workspace, cargo clippy --locked --all-targets -D warnings,
cargo fmt --check: all clean.

Refs #402, map #406.
@blooop

blooop commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

Finding 1 fixed in 4f925e5. The brackets are off strip, home_dir and temp_dir in the module doc; cargo doc -p devlaunch-core --no-deps now names osext in no warning. A short note above the bullets says only env_str is linked because only env_str is pub, so the next reader restores them on purpose or not at all.

Took the one-word edit rather than your finding 2, and the deciding number was one I went looking for after reading it: that cargo doc run emits 112 private-intra-doc-link warnings across the crate. timing ships five, clients::devpod eight, clients::gh more. So the root re-export would dissolve three of 112 and leave the pattern standing in every other module -- tidiness in one place rather than a class of warning gone -- while costing the osext:: qualifier in the snapshot path that you already flagged as a real readability loss.

Two smaller weights on the same side. pub mod osext is the seam #406 asks its question at, and pub use osext::env_str at the root answers "no, the rest does not follow" by construction, so #406 would have to unpick it to answer "yes". And the alternative rewrites public-api.rest.txt for a shape cargo-public-api cannot be run against in this container (nightly), so that snapshot edit would be a guess pushed to CI to adjudicate -- a lot of motion for a cosmetic finding on an approved branch.

Your other two notes are recorded and untouched here: the crate doc still saying "the four layer modules", and the snapshot rows, which your regeneration argument already settles.

Gate on the new head: cargo test --workspace green (including the known flakes in aid/tests/interactive.rs and dl/tests/picker.rs this run), cargo clippy --locked --all-targets -- -D warnings clean, cargo fmt --check clean. No public surface change, so public-api.rest.txt is untouched.

This branch changes what aid does with a value the user set. An undecodable
DEVLAUNCH_AID_AGENT used to read as unset, so the default agent started and the
workspace opened with nothing said about the variable; it is refused by name
now. That is user-visible, and the convention 14 of the last 20 merges follow is
a line under [Unreleased].

[Unreleased] had no Fixed section on this branch, so the entry adds the heading.
Markdown only -- nothing under rust/ is touched, and cargo fmt --check is clean.
@blooop

blooop commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

Added the missing CHANGELOG entry (4e200b7), under a new ### Fixed in ## [Unreleased].

It was owed because this branch changes what aid does with a value the user set, not just how it reads it. An undecodable DEVLAUNCH_AID_AGENT used to read as unset, so the default agent started and the workspace opened with nothing anywhere mentioning the variable that had asked for something else; it is refused by name now. That is user-visible, and the convention 14 of the last 20 merges follow is a line under [Unreleased].

Markdown only -- nothing under rust/ is touched. cargo fmt --check is clean, and no test run was warranted for a CHANGELOG-only commit. There is no changelog job in .github/workflows/, so nothing new is being satisfied here; the entry is the convention, not a gate.

blooop added a commit that referenced this pull request Aug 24, 2026
Review of #407 found the hole by mutation: replace `tty_disabled_by_environment`'s
whole body with `false` -- killing DEVLAUNCH_NO_TTY=1 for every dl and aid there
is -- and `-p aid`, `-p dl` and `-p devlaunch-core` all stay green. Deleting dl's
`any_other_value_is_a_request_for_no_terminal` took the only test constraining
that direction, and the claim that coverage merely "moves to the predicate" was
wrong: the predicate is tested, but nothing said the wrapper consults it.

The pty test now carries the truthy case, asserted by what a skipped prompt does
rather than by waiting out a banner that never comes -- an absence assertion here
would cost the 60-second deadline on the passing path, in a file already in the
#401 flake set. Verified against the same mutant: red with it, green without.

Also corrects a doc claim that described another open branch's state as this
one's. `osext` is `pub(crate)` on this head and on main; #413 proposes exposing
`env_str` and is unmerged. The wrapper's justification never needed that to be
true, so it now says so without depending on it.
@blooop
blooop merged commit 6ce6bdb into main Aug 25, 2026
15 checks passed
@blooop
blooop deleted the fix/aid-agent-env branch August 25, 2026 12:01
blooop pushed a commit that referenced this pull request Aug 25, 2026
Conflict was CHANGELOG.md alone: main's aid-agent entry (#413) and this
branch's two entries both land under [Unreleased]'s second '### Fixed'.
Kept both.
blooop pushed a commit that referenced this pull request Aug 25, 2026
Three resolutions, only one of which git flagged.

CHANGELOG.md: textual conflict, kept both sides.

lifecycle.rs:6986: no textual conflict, but the test main gained in #428
(a_record_removed_while_the_plan_sat_there_is_not_reported_as_re_pointed)
still bound devpod_home as a bare PathBuf where the signatures this branch
rewrites want &DevpodHome. Two E0308s in a tree git called clean. Now
DevpodHome::at(..), matching its eight siblings.

osext.rs: no textual conflict, but system_words was written pub while the
module was pub(crate); #413 has since made osext a pub mod, so the merge
would have added a public-surface row absent from both snapshots and
reddened the public-api job. Demoted to pub(crate) alongside strip,
home_dir and temp_dir, and its doc bullet unbracketed to match. It has no
consumer outside the crate.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant