Skip to content

feat(install): automate shell PATH integration#1687

Merged
Aaronontheweb merged 13 commits into
devfrom
feature/automate-install-path
Jul 18, 2026
Merged

feat(install): automate shell PATH integration#1687
Aaronontheweb merged 13 commits into
devfrom
feature/automate-install-path

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Automatically make installed Netclaw binaries available on PATH while preserving an explicit opt-out:

  • Unix: create ~/.netclaw/env and source it from the detected bash or zsh startup file; write a native fish conf.d snippet.
  • Windows: persist the normalized install directory in the User PATH and update the current installer process without rewriting inherited Machine PATH entries.
  • Support --skip-shell and -SkipShell for callers that do not want installer-managed PATH integration.
  • Document the default behavior and opt-out commands.

Refactoring applied

This revision removes the mock-heavy shell-command tests and narrows the implementation around observable behavior:

  • Shell paths are quoted safely, including spaces and apostrophes.
  • Repeated installs are idempotent.
  • macOS bash startup precedence and zsh's effective ZDOTDIR are respected.
  • Unknown Unix shells are not modified and receive shell-neutral guidance.
  • Windows User, Machine-inherited, and current-process PATH values remain separate.
  • A stale Windows process PATH is repaired even when the User PATH already contains Netclaw.
  • The dependency-free manifest parser uses portable POSIX tools, accepts reordered JSON fields, and selects assets from the requested release.

Independent adversarial hardening

A separate agent reviewed the implementation specifically for shell and PATH mutation hazards. Every actionable finding was fixed:

  • Unix validates the physical path after symlink resolution and before copying binaries or modifying shell files, so a safe-looking symlink cannot introduce :, CR, or LF into persisted PATH data.
  • zsh is queried after it reads .zshenv, so a non-exported ZDOTDIR is honored; unsafe or ambiguous results decline automatic mutation.
  • Unsupported shells receive the literal directory and shell-neutral instructions rather than a Bash command that may be invalid.
  • Windows PowerShell 5.1 no longer calls the modern-only Path.TrimEndingDirectorySeparator API; CI executes the complete smoke suite under both Windows PowerShell 5.1 and PowerShell 7.
  • A literal %NAME% install path is preserved under REG_SZ and explicitly rejected before mutation under REG_EXPAND_SZ, where Windows would expand and corrupt it.
  • The per-user HKCU\\Environment key is opened or created without elevation; the installer never modifies HKLM or the machine PATH.
  • Existing raw User PATH text, REG_SZ/REG_EXPAND_SZ type, %VAR% references, inherited process entries, and empty-PATH semantics remain preserved.

Validation

  • All 32 current GitHub checks pass on head 4da4e50a7.
  • Unix installer smoke: 30/30 locally; Ubuntu and macOS CI additionally execute zsh/fish and physical-path coverage.
  • Windows installer smoke: 24/24 under PowerShell 7 and 24/24 under stock Windows PowerShell 5.1, on both push and pull-request triggers.
  • Full .NET suite: 6,013 passed locally with 14 opt-in integration tests skipped; all six Ubuntu/macOS/Windows CI test jobs pass.
  • Native smoke passes on Linux and macOS for both trigger sets; screenshot regression and native publishing pass.
  • shellcheck scripts/install.sh scripts/smoke/install-smoke.sh
  • PowerShell parser validation
  • dotnet slopwatch analyze: 0 issues
  • pwsh -File ./scripts/Add-FileHeaders.ps1 -Verify
  • git diff --check

Website documentation follow-up: netclaw-dev/netclaw-website#89.

Base automatically changed from fix/attachment-path-contract-1683 to dev July 17, 2026 22:21
Comment thread src/Netclaw.Actors.Tests/Channels/SlackAttachmentLineTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/Channels/SlackAttachmentLineTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/Channels/SlackAttachmentLineTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/Channels/SlackAttachmentLineTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/Channels/SlackAttachmentLineTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/Channels/SlackAttachmentLineTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/Channels/SlackAttachmentLineTests.cs Fixed
Replace manual PATH instructions with automatic shell profile modification.
The installer now detects the user's shell and writes a self-guarding env
script (~/.netclaw/env) that is sourced from the appropriate RC file.

Key changes:
- Add --skip-shell flag to opt out of automatic shell modification
- Detect shell via $SHELL (bash, zsh, fish supported; others get manual instructions)
- Write ~/.netclaw/env with colon-affixed case guard (rustup/fzf pattern)
- Append source line to correct RC: ~/.bashrc (Linux), ~/.profile (macOS bash),
  ~/.zshrc (zsh with ZDOTDIR support), ~/.config/fish/conf.d/netclaw.fish (fish)
- Duplicate prevention: grep -xF guard before appending to RC
- RC file validated with bash -n after modification

Smoke tests (section 9):
- Separate tests for bash-linux, zsh, and fish RC modification
- --skip-shell flag: verify no RC/env file created
- Unknown shell: verify graceful fallback message
- ZDOTDIR: verify zsh respects ZDOTDIR for RC location
- Duplicate prevention: second install adds no extra source lines
Replace manual PATH instructions with automatic User-scope PATH modification.
The installer now prepends the install dir to the User PATH and broadcasts
WM_SETTINGCHANGE to Explorer so new terminal windows pick up the change.

Key changes:
- Add -SkipShell switch to opt out of automatic PATH modification
- Read User-scope PATH (not merged $env:PATH) to avoid Machine entry corruption
- Prepend install dir to User PATH (highest priority)
- Update current session's $env:PATH
- Broadcast WM_SETTINGCHANGE via P/Invoke SendMessageTimeout
- Guard against duplicates: normalize trailing backslash before comparison
- Check 32K character limit before writing, warn if near limit

Smoke tests:
- PATH automation logic: duplicate detection with fake PATH strings
- Trailing backslash normalization
- Null User PATH handled as empty
- -SkipShell flag: verify install completes with skipped message
The real install test now uses a temp HOME directory so shell integration
writes to the temp dir instead of the CI runner's real profile. This also
lets us verify the env script was created and the RC file was modified.

Adds 2 new assertions: env script exists, RC file sources env script.
- bash test: select .profile on macOS (login shell) vs .bashrc on Linux
- fish test: unset XDG_CONFIG_HOME so config path stays within temp HOME
- fish test: add duplicate prevention and re-install assertions
- Windows: remove false-positive regex check on success message
$entries4 is a 0-element array which PowerShell can coerce to $null in
some contexts, causing '$entries4.Count' to throw ParentContainsErrorRecordException.
$(@entries4).Count forces array coercion so .Count always works.
@Aaronontheweb
Aaronontheweb force-pushed the feature/automate-install-path branch from 92b71eb to f7c9bc5 Compare July 18, 2026 14:20
@Aaronontheweb
Aaronontheweb marked this pull request as ready for review July 18, 2026 17:40
@Aaronontheweb
Aaronontheweb enabled auto-merge (squash) July 18, 2026 17:40
@Aaronontheweb
Aaronontheweb merged commit dac46f9 into dev Jul 18, 2026
34 checks passed
@Aaronontheweb
Aaronontheweb deleted the feature/automate-install-path branch July 18, 2026 17:47
Aaronontheweb added a commit that referenced this pull request Jul 18, 2026
….0-alpha.onnx.8 (#1695)

* release: prepare 0.25.0-beta.5 (#1666)

* fix(daemon): bound the daemon-stop session drain and give CLI stop kill headroom (#1673)

Two related shutdown-race defects:

1. The SIGTERM/daemon-stop CoordinatedShutdown drain task called
   SessionDrainHelper.DrainAsync with CancellationToken.None for the
   operation token, so a session parked on interactive tool approval
   (which can never ack) hung the call for the full 200s Akka
   before-service-unbind phase timeout, leaking the abandoned drain
   task. Fixed by bounding the drain with a TimeProvider-driven CTS
   sized to DaemonConfig.BoundedDrainTimeout (GracefulShutdownBudget
   minus a 10s safety margin), so it always finishes before the phase
   timeout fires. The drain's existing timeout logging now also lists
   which sessions didn't drain.

2. `netclaw daemon stop` waited only 10s before force-killing, and
   even after fixing that to match the daemon's own 200s phase
   timeout, the CLI and daemon would race exactly at the boundary
   (production evidence: a daemon force-killed ~100ms from a clean
   exit). Fixed by giving the CLI a 15s grace window to poll for exit
   after its budget elapses before escalating to SIGKILL, and adding
   TimeoutStopSec= to the generated systemd unit so systemd itself
   can't SIGKILL the cgroup out from under a still-waiting
   `netclaw daemon stop` (ExecStop=).

All the shutdown-timing constants now live in one place
(DaemonConfig.GracefulShutdownBudget and friends) so the layering
(bounded drain < Akka phase timeout < CLI budget+grace <
systemd TimeoutStopSec) can't drift out of lockstep again.

Fixes #1664, #1665

* fix(cli): reject numeric model modalities (#1677)

* fix(cli): handle model migration errors without crashing (#1678)

* fix(cli): handle model migration validation errors

* test(configuration): use non-resetting path joins

* fix(cli): report unresolved model references cleanly (#1680)

* chore(deps): bump actions/setup-dotnet from 5 to 6 (#1684)

Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5 to 6.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](actions/setup-dotnet@v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test(tools): remove obsolete shell timeout race (#1688)

* Fix authoritative attachment path guidance (#1686)

* fix(channels): clarify authoritative attachment paths

* chore: remove unnecessary openspec artifacts

* test(evals): cover authoritative attachment paths

* test(evals): stage attachment path fixture

* test(sessions): register session pipeline in shared fixture (#1690)

* test(sessions): make tool result assertion deterministic

* test(sessions): restore tool result regression coverage

* fix(approvals): show MCP arguments in prompts (#1689)

* fix(approvals): show MCP arguments in prompts

* fix(approvals): harden MCP invocation previews

* feat(install): automate shell PATH integration (#1687)

* feat(install): automate shell PATH integration for Unix installers

Replace manual PATH instructions with automatic shell profile modification.
The installer now detects the user's shell and writes a self-guarding env
script (~/.netclaw/env) that is sourced from the appropriate RC file.

Key changes:
- Add --skip-shell flag to opt out of automatic shell modification
- Detect shell via $SHELL (bash, zsh, fish supported; others get manual instructions)
- Write ~/.netclaw/env with colon-affixed case guard (rustup/fzf pattern)
- Append source line to correct RC: ~/.bashrc (Linux), ~/.profile (macOS bash),
  ~/.zshrc (zsh with ZDOTDIR support), ~/.config/fish/conf.d/netclaw.fish (fish)
- Duplicate prevention: grep -xF guard before appending to RC
- RC file validated with bash -n after modification

Smoke tests (section 9):
- Separate tests for bash-linux, zsh, and fish RC modification
- --skip-shell flag: verify no RC/env file created
- Unknown shell: verify graceful fallback message
- ZDOTDIR: verify zsh respects ZDOTDIR for RC location
- Duplicate prevention: second install adds no extra source lines

* feat(install): automate PATH modification for Windows installer

Replace manual PATH instructions with automatic User-scope PATH modification.
The installer now prepends the install dir to the User PATH and broadcasts
WM_SETTINGCHANGE to Explorer so new terminal windows pick up the change.

Key changes:
- Add -SkipShell switch to opt out of automatic PATH modification
- Read User-scope PATH (not merged $env:PATH) to avoid Machine entry corruption
- Prepend install dir to User PATH (highest priority)
- Update current session's $env:PATH
- Broadcast WM_SETTINGCHANGE via P/Invoke SendMessageTimeout
- Guard against duplicates: normalize trailing backslash before comparison
- Check 32K character limit before writing, warn if near limit

Smoke tests:
- PATH automation logic: duplicate detection with fake PATH strings
- Trailing backslash normalization
- Null User PATH handled as empty
- -SkipShell flag: verify install completes with skipped message

* fix(smoke): isolate real install test to temp HOME

The real install test now uses a temp HOME directory so shell integration
writes to the temp dir instead of the CI runner's real profile. This also
lets us verify the env script was created and the RC file was modified.

Adds 2 new assertions: env script exists, RC file sources env script.

* fix(smoke): cross-platform install smoke test fixes

- bash test: select .profile on macOS (login shell) vs .bashrc on Linux
- fish test: unset XDG_CONFIG_HOME so config path stays within temp HOME
- fish test: add duplicate prevention and re-install assertions
- Windows: remove false-positive regex check on success message

* fix(smoke): use @() array coercion for empty PATH entries count

$entries4 is a 0-element array which PowerShell can coerce to $null in
some contexts, causing '$entries4.Count' to throw ParentContainsErrorRecordException.
$(@entries4).Count forces array coercion so .Count always works.

* fix(install): validate PATH integration through real shells

* fix(install): make manifest fallback portable

* Harden installer PATH mutations

* fix(smoke): compare persisted physical paths

* fix(installer): harden shell path mutation

* test(installer): support Windows PowerShell path joins

* test(installer): normalize manifest fixture encoding

* test(installer): capture expected PowerShell failures

* release: prepare 0.25.0 stable (#1692)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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