Skip to content

fix(cli): release sandbox forwards on full stop - #6395

Closed
chengjiew wants to merge 1 commit into
mainfrom
fix/6392_stop_port_forward
Closed

fix(cli): release sandbox forwards on full stop#6395
chengjiew wants to merge 1 commit into
mainfrom
fix/6392_stop_port_forward

Conversation

@chengjiew

@chengjiew chengjiew commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR makes deprecated full stop release OpenShell forwards owned by the selected sandbox before releasing the host gateway. It fixes nemohermes stop leaving the Hermes API forward on port 8642 alive and makes the in-sandbox gateway stop message use the active agent product name.

Related Issue

Fixes #6392

Changes

  • Enumerate live openshell forward list entries during legacy full stop and stop only forwards owned by the selected sandbox using forward stop <port> <sandbox>.
  • Keep foreign sandbox forwards untouched even when they use nearby managed ports.
  • Replace hard-coded OpenClaw gateway stop-starting copy with the active agent product name.
  • Add regression coverage for releasing Hermes-style port 8642 and another owned forward while preserving another sandbox's forward.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: existing help already states deprecated full stop releases the managed gateway port; this PR aligns implementation with that documented behavior.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: self-review; stop is sandbox-scoped by openshell forward list ownership and uses forward stop <port> <sandbox> to avoid collateral teardown.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Verification

  • PR description includes the DCO sign-off declaration and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run check:diff passed when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: npm test -- src/lib/tunnel/services-sandbox.test.ts src/lib/tunnel/services-gateway-ownership.test.ts src/lib/tunnel/service-command.test.ts passed, 3 files / 36 tests.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result:
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Chengjie Wang chengjiew@nvidia.com

Summary by CodeRabbit

  • Bug Fixes
    • Improved gateway port release so stopping one sandbox now clears only that sandbox’s managed host forwards, including active ports, without affecting others.
    • Updated stop behavior to use the product name consistently in status messages.
    • Added clearer logging when forwarded ports are released.

Signed-off-by: Chengjie Wang <chengjiew@nvidia.com>
@chengjiew chengjiew self-assigned this Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a stopSandboxForwards helper in services.ts that queries and stops openshell-managed host forwards owned by a specific sandbox during the deprecated full stopAll flow when releaseGatewayPort is enabled, updates a gateway log message to use AGENT_PRODUCT_NAME, and adds a corresponding test.

Changes

Gateway Port Release Fix

Layer / File(s) Summary
stopSandboxForwards implementation and wiring
src/lib/tunnel/services.ts
Imports OpenShell timeout constants and getOccupiedPorts, replaces a hard-coded "OpenClaw" log string with AGENT_PRODUCT_NAME, adds stopSandboxForwards(sandboxName) to list forwards, filter sandbox-owned ports, stop them via openshell forward stop, and logs the count; wires this call into stopAll before releaseGatewayPortForStop.
Test coverage for sandbox-scoped forward stop
src/lib/tunnel/services-sandbox.test.ts
Adds a stopAll test mocking spawnSync and resolveOpenshell to verify only the selected sandbox's managed host forwards are stopped and the release count is logged.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as stopAll
  participant Svc as stopSandboxForwards
  participant OpenShell
  participant GatewayStop as releaseGatewayPortForStop

  CLI->>Svc: stopSandboxForwards(sandboxName)
  Svc->>OpenShell: openshell forward list
  OpenShell-->>Svc: list of forwards
  Svc->>Svc: filter ports via getOccupiedPorts(sandboxName)
  loop for each matching port
    Svc->>OpenShell: openshell forward stop
  end
  Svc-->>CLI: log "Released N managed host forward(s)"
  CLI->>GatewayStop: releaseGatewayPortForStop(...)
Loading

Suggested labels: bug-fix, area: cli

Suggested reviewers: ericksoa, cjagwani

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: releasing sandbox forwards during full stop.
Linked Issues check ✅ Passed The changes release sandbox-owned forwards on stop and fix the Hermes/OpenClaw stop message as required by issue #6392.
Out of Scope Changes check ✅ Passed The added helper, log tweak, and regression test all align with the linked stop-flow fix and introduce no obvious unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/6392_stop_port_forward

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

@github-code-quality

github-code-quality Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in the fix/6392_stop_port_f... branch is 96%. Coverage data for the main branch is not yet available.

Show a code coverage summary of the most covered files.
File main fix/6392_stop_port_f... bcb4a64 +/-
nemoclaw/src/se...cret-scanner.ts 100%
nemoclaw/src/commands/slash.ts 100%
nemoclaw/src/li...bprocess-env.ts 100%
nemoclaw/src/bl...eprint/state.ts 98%
nemoclaw/src/onboard/config.ts 98%
nemoclaw/src/bl...int/snapshot.ts 97%
nemoclaw/src/bl...print/runner.ts 95%
nemoclaw/src/co...ration-state.ts 94%
nemoclaw/src/bl...ate-networks.ts 94%
nemoclaw/src/index.ts 94%

TypeScript / code-coverage/cli

The overall coverage in the fix/6392_stop_port_f... branch is 75%. Coverage data for the main branch is not yet available.

Show a code coverage summary of the most covered files.
File main fix/6392_stop_port_f... bcb4a64 +/-
src/lib/shields...nsition-lock.ts 85%
src/lib/onboard/preflight.ts 83%
src/lib/actions...all/run-plan.ts 81%
src/lib/state/o...oard-session.ts 80%
src/lib/actions...licy-channel.ts 79%
src/lib/state/sandbox.ts 74%
src/lib/onboard...er-gpu-patch.ts 70%
src/lib/shields/index.ts 67%
src/lib/policy/index.ts 67%
src/lib/onboard.ts 28%

Updated July 07, 2026 10:57 UTC
Code Coverage is in Public Preview. Learn more and provide us with your feedback.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — Changes requested

Merge posture: Do not merge yet
Primary next action: Fix PRA-2: Verify `forward stop` results before reporting released forwards; then add or justify PRA-T1.
Open items: 1 required · 1 warning · 0 suggestions · 8 test follow-ups
Top item: Verify `forward stop` results before reporting released forwards

Action checklist

  • PRA-2 Fix: Verify `forward stop` results before reporting released forwards in src/lib/tunnel/services.ts:618
  • PRA-1 Resolve or justify: Source-of-truth review needed: Deprecated full stop cleanup of OpenShell-managed host forwards
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Runtime validation
  • PRA-T3 Add or justify test follow-up: Runtime validation
  • PRA-T4 Add or justify test follow-up: Acceptance clause
  • PRA-T5 Add or justify test follow-up: Acceptance clause
  • PRA-T6 Add or justify test follow-up: Acceptance clause
  • PRA-T7 Add or justify test follow-up: Acceptance clause
  • PRA-T8 Add or justify test follow-up: Acceptance clause

Findings index

ID Severity Category Location Required action
PRA-1 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-2 Required correctness src/lib/tunnel/services.ts:618 Capture each `forward stop` result and only count confirmed successful stops. Warn with the port and sandbox when a stop exits nonzero, times out, or has a spawn error; avoid the unconditional success message when any selected forward was not confirmed released. If OpenShell exposes a reliable post-stop list/probe, consider re-listing or otherwise confirming the selected ports are no longer live before logging success.

🚨 Required before merge

Address these before merging unless a maintainer explicitly overrides the advisor with rationale.

PRA-2 Required — Verify `forward stop` results before reporting released forwards

  • Location: src/lib/tunnel/services.ts:618
  • Category: correctness
  • Problem: `stopSandboxForwards` builds the selected sandbox's live forward list, calls `openshell forward stop` for each port, and then logs `Released N managed host forward(s)` based only on the number of attempted stops. The `spawnSync` result for each stop is ignored, so a nonzero exit, timeout, or spawn error still produces successful cleanup output.
  • Impact: The linked bug can persist on the failure path: `nemohermes stop` may exit after saying forwards were released while port 8642 or another managed SSH forward remains bound. That is both a correctness problem and a sandbox lifecycle/security posture problem because unexpected host forwards can remain reachable and can cause later port conflicts.
  • Required action: Capture each `forward stop` result and only count confirmed successful stops. Warn with the port and sandbox when a stop exits nonzero, times out, or has a spawn error; avoid the unconditional success message when any selected forward was not confirmed released. If OpenShell exposes a reliable post-stop list/probe, consider re-listing or otherwise confirming the selected ports are no longer live before logging success.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read `src/lib/tunnel/services.ts` in `stopSandboxForwards`: the `for (const port of ports)` loop calls `spawnSync(...)` and discards its return value, while the following `info(...)` logs the full attempted count.
  • Missing regression test: Add a unit test such as `warns and does not report released forwards when openshell forward stop fails or times out`: mock `forward list` to return `test-sb 127.0.0.1 8642 ... running`, mock the corresponding `forward stop` call with `{ status: 1, stderr: 'failed' }` or a timeout-like null status/error, then assert a warning is emitted and the output does not claim `Released 1 managed host forward(s)`.
  • Done when: The required change is committed and verification passes: Read `src/lib/tunnel/services.ts` in `stopSandboxForwards`: the `for (const port of ports)` loop calls `spawnSync(...)` and discards its return value, while the following `info(...)` logs the full attempted count.
  • Evidence: The new test covers only the happy path where all later `spawnSync` calls default to `{ status: 0 }`; no test covers a failed `openshell forward stop`.
Review findings by urgency: 1 required fix, 1 item to resolve/justify, 0 in-scope improvements

⚠️ Resolve or justify before merge

Investigate these in the current review; either fix them, explain why they are not applicable, or document the accepted risk.

PRA-1 Resolve/justify — Source-of-truth review needed: Deprecated full stop cleanup of OpenShell-managed host forwards

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: The new unit test proves selected sandbox forwards 8642 and 18790 are targeted and a foreign sandbox's 18789 forward is preserved.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: The source-of-truth gap is that the code trusts the prior list and attempted stop calls, but does not verify or inspect `forward stop` outcomes before reporting release.

💡 In-scope improvements

These are lower-risk, not throwaway. Prefer fixing them in this PR when they are local to changed code; defer only with rationale or a linked follow-up.

  • None.
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Runtime validation — Unit: `warns and does not report released forwards when openshell forward stop fails or times out`.. This PR changes sandbox/host networking teardown. Unit coverage checks command construction and sandbox filtering, but the linked macOS failure depends on real OpenShell ssh-proxy process cleanup and host port release.
  • PRA-T2 Runtime validation — Unit: `uses active Hermes product name in the in-sandbox gateway stop message` by loading branding/services with `NEMOCLAW_AGENT=hermes` or an equivalent existing branding test seam.. This PR changes sandbox/host networking teardown. Unit coverage checks command construction and sandbox filtering, but the linked macOS failure depends on real OpenShell ssh-proxy process cleanup and host port release.
  • PRA-T3 Runtime validation — Runtime/integration: on macOS/OpenShell, after onboarding a Hermes sandbox and running `nemohermes stop`, assert `lsof -ti :8642 -sTCP:LISTEN` becomes empty within 5 seconds and no `openshell ssh-proxy` process remains with `-L 127.0.0.1:8642` for that sandbox.. This PR changes sandbox/host networking teardown. Unit coverage checks command construction and sandbox filtering, but the linked macOS failure depends on real OpenShell ssh-proxy process cleanup and host port release.
  • PRA-T4 Acceptance clause — [macOS][CLI&UX] nemohermes stop does not release port 8642 — SSH port-forward process survives after deprecated full stop — add test evidence or identify existing coverage. `stopAll` now calls `stopSandboxForwards(sandboxName)` when `releaseGatewayPort` is true, and the new unit test expects `openshell forward stop 8642 test-sb`. The implementation still ignores stop failures, so the surviving-forward path is not fully closed.
  • PRA-T5 Acceptance clause — `nemohermes stop` is documented as "Deprecated full stop (also releases the managed gateway port)" but does not release port 8642. — add test evidence or identify existing coverage. `commands/stop.ts` already documents the deprecated full stop behavior, and this PR wires forward cleanup into `stopAll(... releaseGatewayPort: true)`. Confirmation of successful release is missing.
  • PRA-T6 Acceptance clause — After the command completes, an SSH port-forward process (`openshell ssh-proxy`) continues to hold port 8642. — add test evidence or identify existing coverage. The diff uses the OpenShell source-of-truth command `forward stop <port> <sandbox>` for owned forwards, but there is no check that the command killed the ssh-proxy process or that port 8642 became unbound.
  • PRA-T7 Acceptance clause — Platform scope: Reproduced on macOS 26.5 (Apple Silicon, arm64) only; other platforms not tested. — add test evidence or identify existing coverage. The code path is platform-neutral and uses OpenShell CLI commands, but no read-only evidence proves the macOS runtime behavior.
  • PRA-T8 Acceptance clause — Regression: Unknown — earlier versions not tested. — add test evidence or identify existing coverage. No acceptance action is implied beyond the linked fix; the PR adds a regression unit test for the selected-sandbox forward-stop command sequence.

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor (Nemotron Ultra) — Changes requested

Merge posture: Do not merge yet
Primary next action: Fix PRA-2: Monolith growth: services.ts exceeds 20-line threshold; then add or justify PRA-T1.
Open items: 3 required · 2 warnings · 2 suggestions · 4 test follow-ups
Since last review: 0 prior items resolved · 0 still apply · 7 new items found

Action checklist

  • PRA-2 Fix: Monolith growth: services.ts exceeds 20-line threshold in src/lib/tunnel/services.ts:1
  • PRA-3 Fix: Monolith growth: services-sandbox.test.ts exceeds 20-line threshold in src/lib/tunnel/services-sandbox.test.ts:1
  • PRA-5 Fix: Forward cleanup implemented but has silent-failure gap in src/lib/tunnel/services.ts:613
  • PRA-1 Resolve or justify: Silent failure on individual forward stop calls in src/lib/tunnel/services.ts:622
  • PRA-4 Resolve or justify: Forward stop error visibility gap in src/lib/tunnel/services.ts:618
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Runtime validation
  • PRA-T3 Add or justify test follow-up: Missing error-path tests for stopSandboxForwards
  • PRA-T4 Add or justify test follow-up: Acceptance clause
  • PRA-6 In-scope improvement: Missing error-path tests for stopSandboxForwards in src/lib/tunnel/services-sandbox.test.ts:261
  • PRA-7 In-scope improvement: Uses named timeout constants consistently in src/lib/tunnel/services.ts:613

Findings index

ID Severity Category Location Required action
PRA-1 Resolve/justify security src/lib/tunnel/services.ts:622 Capture each `spawnSync` result. Track successful vs failed stops. Emit a warning for any failures and adjust the info log to reflect actual releases.
PRA-2 Required correctness src/lib/tunnel/services.ts:1 Extract forward management (`stopSandboxForwards`, `getOccupiedPorts` usage) into a dedicated module (e.g., `src/lib/tunnel/forward-cleanup.ts` or `src/lib/onboard/forward-cleanup.ts`) to offset growth. Alternatively, move to `gateway-port-release.ts` which already handles port-scoped gateway release.
PRA-3 Required correctness src/lib/tunnel/services-sandbox.test.ts:1 Split sandbox-stop tests into dedicated files: `services-stop-kubectl.test.ts`, `services-stop-fallback.test.ts`, `services-stop-forwards.test.ts`, `services-stop-env-resolution.test.ts`.
PRA-4 Resolve/justify correctness src/lib/tunnel/services.ts:618 Check each `spawnSync` result. Track successful vs failed stops. Update the info log to reflect actual releases, and emit a warning for any failures.
PRA-5 Required acceptance src/lib/tunnel/services.ts:613 Fix the silent-failure issue (see finding #1) to fully satisfy acceptance. The test added already validates correct filtering and command invocation.
PRA-6 Improvement tests src/lib/tunnel/services-sandbox.test.ts:261 Add four tests for the error scenarios above. Each should assert appropriate warn/info logs and that other ports still get processed.
PRA-7 Improvement correctness src/lib/tunnel/services.ts:613 No action needed. Pattern is consistent with other openshell call sites.

🚨 Required before merge

Address these before merging unless a maintainer explicitly overrides the advisor with rationale.

PRA-2 Required — Monolith growth: services.ts exceeds 20-line threshold

  • Location: src/lib/tunnel/services.ts:1
  • Category: correctness
  • Problem: File grew by 41 lines (749 → 790) with the addition of `stopSandboxForwards` (35 lines) and imports. This exceeds the 20-line monolith growth threshold for a single PR.
  • Impact: Continues accumulation of tunnel/service logic in a single large module, reducing maintainability and increasing coupling between cloudflared, gateway stop, and forward management.
  • Required action: Extract forward management (`stopSandboxForwards`, `getOccupiedPorts` usage) into a dedicated module (e.g., `src/lib/tunnel/forward-cleanup.ts` or `src/lib/onboard/forward-cleanup.ts`) to offset growth. Alternatively, move to `gateway-port-release.ts` which already handles port-scoped gateway release.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Run `wc -l src/lib/tunnel/services.ts` — should be ≤769 lines after extraction.
  • Missing regression test: No test needed — structural enforcement via line-count check in CI.
  • Done when: The required change is committed and verification passes: Run `wc -l src/lib/tunnel/services.ts` — should be ≤769 lines after extraction.
  • Evidence: Diff shows +43/-1 net lines; drift context flags severity=blocker with rationale "Current monolith grew by 20 or more lines".

PRA-3 Required — Monolith growth: services-sandbox.test.ts exceeds 20-line threshold

  • Location: src/lib/tunnel/services-sandbox.test.ts:1
  • Category: correctness
  • Problem: Test file grew by 43 lines (454 → 497), exceeding the 20-line monolith growth threshold.
  • Impact: Test file becoming unwieldy; mixing kubectl path, fallback path, forward release, and env-var resolution tests in one file.
  • Required action: Split sandbox-stop tests into dedicated files: `services-stop-kubectl.test.ts`, `services-stop-fallback.test.ts`, `services-stop-forwards.test.ts`, `services-stop-env-resolution.test.ts`.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Run `wc -l src/lib/tunnel/services-sandbox.test.ts` — should be ≤474 lines after split.
  • Missing regression test: No test needed — structural enforcement via line-count check in CI.
  • Done when: The required change is committed and verification passes: Run `wc -l src/lib/tunnel/services-sandbox.test.ts` — should be ≤474 lines after split.
  • Evidence: Diff shows +43 lines; drift context flags severity=blocker with same rationale.

PRA-5 Required — Forward cleanup implemented but has silent-failure gap

Review findings by urgency: 3 required fixes, 2 items to resolve/justify, 2 in-scope improvements

⚠️ Resolve or justify before merge

Investigate these in the current review; either fix them, explain why they are not applicable, or document the accepted risk.

PRA-1 Resolve/justify — Silent failure on individual forward stop calls

  • Location: src/lib/tunnel/services.ts:622
  • Category: security
  • Problem: The `stopSandboxForwards` loop calls `spawnSync` with `stdio: "ignore"` and does not check result status. If `openshell forward stop` fails for any port (permission denied, timeout, openshell not found mid-loop), the operator sees no warning — only the aggregate info log reporting the filtered port count as "released".
  • Impact: Operator believes all sandbox forwards were released when some may still be bound, leaving stale SSH port-forward processes (e.g., the reported port 8642) alive after `nemoclaw stop` / `nemohermes stop`.
  • Recommended action: Capture each `spawnSync` result. Track successful vs failed stops. Emit a warning for any failures and adjust the info log to reflect actual releases.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Mock `spawnSync` to return `{ status: 1 }` for one `forward stop` call in the test and verify a warning is logged and the released count excludes that port.
  • Missing regression test: Add test mocking `spawnSync` to return non-zero status for one `forward stop` call and assert a warning is logged and the released count is correct.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Mock `spawnSync` to return `{ status: 1 }` for one `forward stop` call in the test and verify a warning is logged and the released count excludes that port.
  • Evidence: Line 618-624 in services.ts: loop uses `stdio: "ignore"`, no result capture, info log uses `ports.length` from filtered list not actual stop results.

PRA-4 Resolve/justify — Forward stop error visibility gap

  • Location: src/lib/tunnel/services.ts:618
  • Category: correctness
  • Problem: The `stopSandboxForwards` loop uses `stdio: "ignore"` and no result capture. Even if `forward stop` fails (non-zero exit, timeout, openshell not found mid-loop), the function continues silently and reports `Released N managed host forward(s)` based only on the filtered port count, not actual stop success.
  • Impact: Misleading success message; operator cannot distinguish partial from complete release without manual `openshell forward list` verification.
  • Recommended action: Check each `spawnSync` result. Track successful vs failed stops. Update the info log to reflect actual releases, and emit a warning for any failures.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Mock `spawnSync` to return `{ status: 1 }` for one port and verify warning appears and released count excludes failed port.
  • Missing regression test: Test that failed `forward stop` produces a warning and doesn't inflate the released count.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Mock `spawnSync` to return `{ status: 1 }` for one port and verify warning appears and released count excludes failed port.
  • Evidence: Same code region as security finding ci: auto-update release notes on push to main #1; categorized as quality for error-visibility UX concern.

💡 In-scope improvements

These are lower-risk, not throwaway. Prefer fixing them in this PR when they are local to changed code; defer only with rationale or a linked follow-up.

PRA-6 Improvement — Missing error-path tests for stopSandboxForwards

  • Location: src/lib/tunnel/services-sandbox.test.ts:261
  • Category: tests
  • Problem: Current test covers happy path and correct filtering. Missing edge-case tests for: (1) `openshell forward list` returns non-zero exit, (2) `openshell forward stop` returns non-zero for one port, (3) `openshell forward stop` times out, (4) `resolveOpenshell` returns null.
  • Impact: Error handling behavior is unverified; silent failures (finding ci: auto-update release notes on push to main #1) would not be caught by regression tests.
  • Suggested action: Add four tests for the error scenarios above. Each should assert appropriate warn/info logs and that other ports still get processed.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Run test file and confirm only 1 test for `stopSandboxForwards` path exists (the new one).
  • Missing regression test: Four new tests: (1) forward list failure → warns and returns, (2) forward stop failure for one port → warns and continues, (3) forward stop timeout → warns and continues, (4) resolveOpenshell returns null → warns and returns.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Static test inventory shows 19 named test blocks but only 1 for the new forward-release path.

PRA-7 Improvement — Uses named timeout constants consistently

  • Location: src/lib/tunnel/services.ts:613
  • Category: correctness
  • Problem: Uses `OPENSHELL_PROBE_TIMEOUT_MS` (15s) for `forward list` and `OPENSHELL_OPERATION_TIMEOUT_MS` (30s) for `forward stop`. Follows established pattern in `timeouts.ts` — no custom magic numbers.
  • Impact: Positive: consistent timeout categorization across openshell call sites.
  • Suggested action: No action needed. Pattern is consistent with other openshell call sites.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: grep -r "OPENSHELL_.*_TIMEOUT_MS" src/lib/tunnel/ shows consistent usage.
  • Missing regression test: None.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Lines 28 (import), 613, 618 use the imported constants.
Simplification opportunities: 1 possible cut

These are safe simplification checks only. Do not remove validation, security controls, data-loss prevention, or required tests.

  • PRA-7 native (src/lib/tunnel/services.ts:613): No custom timeout literals
    • Replacement: Named constants from ../adapters/openshell/timeouts
    • Net: 0 lines
    • Safety boundary: Timeout values remain configurable centrally; no security boundary weakened
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Runtime validation — Add error-path unit tests for: forward list failure, forward stop failure per port, forward stop timeout, resolveOpenshell returns null. Runtime/sandbox/infrastructure paths need behavioral runtime validation. The stopSandboxForwards function spawns openshell CLI commands that interact with real OpenShell sandbox forwards; unit mocks cannot fully validate the integration.
  • PRA-T2 Runtime validation — Add targeted runtime validation: run nemoclaw stop against a sandbox with active forwards and verify all forwards released via openshell forward list. Runtime/sandbox/infrastructure paths need behavioral runtime validation. The stopSandboxForwards function spawns openshell CLI commands that interact with real OpenShell sandbox forwards; unit mocks cannot fully validate the integration.
  • PRA-T3 Missing error-path tests for stopSandboxForwards — Add four tests for the error scenarios above. Each should assert appropriate warn/info logs and that other ports still get processed.
  • PRA-T4 Acceptance clause — Port 8642 released; SSH port-forward killed (issue [macOS][CLI&UX] nemohermes stop does not release port 8642 — SSH port-forward process survives after deprecated full stop #6392 primary symptom) — add test evidence or identify existing coverage. stopSandboxForwards() enumerates openshell forward list, filters by sandbox, calls forward stop <port> <sandbox>. New test verifies ports 8642 & 18790 stopped for target sandbox; other-sb's 18789 untouched. Silent-failure gap means partial release not detected.
Since last review details

Current findings, using the urgency labels above:

PRA-1 Resolve/justify — Silent failure on individual forward stop calls

  • Location: src/lib/tunnel/services.ts:622
  • Category: security
  • Problem: The `stopSandboxForwards` loop calls `spawnSync` with `stdio: "ignore"` and does not check result status. If `openshell forward stop` fails for any port (permission denied, timeout, openshell not found mid-loop), the operator sees no warning — only the aggregate info log reporting the filtered port count as "released".
  • Impact: Operator believes all sandbox forwards were released when some may still be bound, leaving stale SSH port-forward processes (e.g., the reported port 8642) alive after `nemoclaw stop` / `nemohermes stop`.
  • Recommended action: Capture each `spawnSync` result. Track successful vs failed stops. Emit a warning for any failures and adjust the info log to reflect actual releases.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Mock `spawnSync` to return `{ status: 1 }` for one `forward stop` call in the test and verify a warning is logged and the released count excludes that port.
  • Missing regression test: Add test mocking `spawnSync` to return non-zero status for one `forward stop` call and assert a warning is logged and the released count is correct.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Mock `spawnSync` to return `{ status: 1 }` for one `forward stop` call in the test and verify a warning is logged and the released count excludes that port.
  • Evidence: Line 618-624 in services.ts: loop uses `stdio: "ignore"`, no result capture, info log uses `ports.length` from filtered list not actual stop results.

PRA-2 Required — Monolith growth: services.ts exceeds 20-line threshold

  • Location: src/lib/tunnel/services.ts:1
  • Category: correctness
  • Problem: File grew by 41 lines (749 → 790) with the addition of `stopSandboxForwards` (35 lines) and imports. This exceeds the 20-line monolith growth threshold for a single PR.
  • Impact: Continues accumulation of tunnel/service logic in a single large module, reducing maintainability and increasing coupling between cloudflared, gateway stop, and forward management.
  • Required action: Extract forward management (`stopSandboxForwards`, `getOccupiedPorts` usage) into a dedicated module (e.g., `src/lib/tunnel/forward-cleanup.ts` or `src/lib/onboard/forward-cleanup.ts`) to offset growth. Alternatively, move to `gateway-port-release.ts` which already handles port-scoped gateway release.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Run `wc -l src/lib/tunnel/services.ts` — should be ≤769 lines after extraction.
  • Missing regression test: No test needed — structural enforcement via line-count check in CI.
  • Done when: The required change is committed and verification passes: Run `wc -l src/lib/tunnel/services.ts` — should be ≤769 lines after extraction.
  • Evidence: Diff shows +43/-1 net lines; drift context flags severity=blocker with rationale "Current monolith grew by 20 or more lines".

PRA-3 Required — Monolith growth: services-sandbox.test.ts exceeds 20-line threshold

  • Location: src/lib/tunnel/services-sandbox.test.ts:1
  • Category: correctness
  • Problem: Test file grew by 43 lines (454 → 497), exceeding the 20-line monolith growth threshold.
  • Impact: Test file becoming unwieldy; mixing kubectl path, fallback path, forward release, and env-var resolution tests in one file.
  • Required action: Split sandbox-stop tests into dedicated files: `services-stop-kubectl.test.ts`, `services-stop-fallback.test.ts`, `services-stop-forwards.test.ts`, `services-stop-env-resolution.test.ts`.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Run `wc -l src/lib/tunnel/services-sandbox.test.ts` — should be ≤474 lines after split.
  • Missing regression test: No test needed — structural enforcement via line-count check in CI.
  • Done when: The required change is committed and verification passes: Run `wc -l src/lib/tunnel/services-sandbox.test.ts` — should be ≤474 lines after split.
  • Evidence: Diff shows +43 lines; drift context flags severity=blocker with same rationale.

PRA-4 Resolve/justify — Forward stop error visibility gap

  • Location: src/lib/tunnel/services.ts:618
  • Category: correctness
  • Problem: The `stopSandboxForwards` loop uses `stdio: "ignore"` and no result capture. Even if `forward stop` fails (non-zero exit, timeout, openshell not found mid-loop), the function continues silently and reports `Released N managed host forward(s)` based only on the filtered port count, not actual stop success.
  • Impact: Misleading success message; operator cannot distinguish partial from complete release without manual `openshell forward list` verification.
  • Recommended action: Check each `spawnSync` result. Track successful vs failed stops. Update the info log to reflect actual releases, and emit a warning for any failures.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Mock `spawnSync` to return `{ status: 1 }` for one port and verify warning appears and released count excludes failed port.
  • Missing regression test: Test that failed `forward stop` produces a warning and doesn't inflate the released count.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Mock `spawnSync` to return `{ status: 1 }` for one port and verify warning appears and released count excludes failed port.
  • Evidence: Same code region as security finding ci: auto-update release notes on push to main #1; categorized as quality for error-visibility UX concern.

PRA-5 Required — Forward cleanup implemented but has silent-failure gap

PRA-6 Improvement — Missing error-path tests for stopSandboxForwards

  • Location: src/lib/tunnel/services-sandbox.test.ts:261
  • Category: tests
  • Problem: Current test covers happy path and correct filtering. Missing edge-case tests for: (1) `openshell forward list` returns non-zero exit, (2) `openshell forward stop` returns non-zero for one port, (3) `openshell forward stop` times out, (4) `resolveOpenshell` returns null.
  • Impact: Error handling behavior is unverified; silent failures (finding ci: auto-update release notes on push to main #1) would not be caught by regression tests.
  • Suggested action: Add four tests for the error scenarios above. Each should assert appropriate warn/info logs and that other ports still get processed.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Run test file and confirm only 1 test for `stopSandboxForwards` path exists (the new one).
  • Missing regression test: Four new tests: (1) forward list failure → warns and returns, (2) forward stop failure for one port → warns and continues, (3) forward stop timeout → warns and continues, (4) resolveOpenshell returns null → warns and returns.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Static test inventory shows 19 named test blocks but only 1 for the new forward-release path.

PRA-7 Improvement — Uses named timeout constants consistently

  • Location: src/lib/tunnel/services.ts:613
  • Category: correctness
  • Problem: Uses `OPENSHELL_PROBE_TIMEOUT_MS` (15s) for `forward list` and `OPENSHELL_OPERATION_TIMEOUT_MS` (30s) for `forward stop`. Follows established pattern in `timeouts.ts` — no custom magic numbers.
  • Impact: Positive: consistent timeout categorization across openshell call sites.
  • Suggested action: No action needed. Pattern is consistent with other openshell call sites.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: grep -r "OPENSHELL_.*_TIMEOUT_MS" src/lib/tunnel/ shows consistent usage.
  • Missing regression test: None.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Lines 28 (import), 613, 618 use the imported constants.

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: tunnel-lifecycle, concurrent-gateway-ports
Optional E2E: sandbox-operations

Dispatch hint: tunnel-lifecycle,concurrent-gateway-ports

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

  • tunnel-lifecycle (high): Validates the real services.ts tunnel stop path against Docker/OpenShell onboarding, host cloudflared, status URL reporting, and stop cleanup after changes to stopAll/stopSandboxChannels.
  • concurrent-gateway-ports (high): Closest existing live coverage for managed OpenShell forwards and gateway ownership across multiple sandboxes; it verifies distinct dashboard forwards/gateways coexist and that destroying one sandbox does not break the other.

Optional E2E

  • sandbox-operations (high): Adjacent confidence for sandbox destroy/list/status and gateway recovery lifecycle behavior, including multi-sandbox cleanup, but it does not directly exercise the deprecated full-stop managed-forward release branch.

New E2E recommendations

  • deprecated full stop managed forwards (high): No existing E2E appears to directly exercise nemoclaw stop with releaseGatewayPort=true after live onboarding, then assert openshell forward list no longer contains forwards owned by the selected sandbox while forwards for another sandbox remain intact.
    • Suggested test: Add a focused live E2E job such as deprecated-stop-managed-forwards that onboards two sandboxes, captures OpenShell managed forwards, runs deprecated nemoclaw stop for one sandbox, and verifies only that sandbox's forwards and gateway port are released.

Dispatch hint

  • Workflow: .github/workflows/e2e.yaml
  • jobs input: tunnel-lifecycle,concurrent-gateway-ports

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Target Recommendation

Required E2E targets: state-backup-restore
Optional E2E targets: None

Dispatch required E2E targets:

  • gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=state-backup-restore

Workflow run

Full E2E target advisor summary

E2E Target Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E targets

  • state-backup-restore: The PR changes src/lib/tunnel/services.ts stopAll releaseGatewayPort handling to list and stop managed OpenShell forwards for the selected sandbox. The state-backup-restore free-standing E2E job is wired in e2e.yaml and exercises a real onboarded sandbox followed by nemoclaw stop, which is the legacy full-stop path that sets releaseGatewayPort.
    • Dispatch: gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=state-backup-restore

Optional E2E targets

  • None.

Relevant changed files

  • src/lib/tunnel/services.ts
  • src/lib/tunnel/services-sandbox.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/lib/tunnel/services-sandbox.test.ts (2)

245-245: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test title lacks issue-number suffix.

If this fix is tracked by a local issue (the linked issue this PR addresses), the title should end with a (#1234)-style reference per repo convention.

As per coding guidelines, **/*.test.ts: "Write behavior-oriented test titles, and put local issue references in a final (#1234) suffix."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/tunnel/services-sandbox.test.ts` at line 245, Update the test title
in services-sandbox.test.ts so it follows the repo’s test naming convention by
ending with the local issue reference suffix in the form (`#1234`). Keep the title
behavior-oriented, and apply the change to the existing it(...) block for the
deprecated full stop case.

Source: Coding guidelines


247-259: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mock sequence is positionally coupled to unrelated upstream spawnSync calls.

The list-output mock is the 3rd mockReturnValueOnce, relying on stopSandboxChannelsViaKubectl and the "sandbox exec" fallback each making exactly one spawnSync call before stopSandboxForwards runs. If that call count/order shifts later, this mock would silently attach to the wrong call.

Since "If the return value depends on the arguments passed to the mocked function, use mockImplementation()", keying the response off the command arguments (e.g., ["forward", "list"] vs. others) would make this test robust to unrelated upstream call-order changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/tunnel/services-sandbox.test.ts` around lines 247 - 259, The test’s
spawnSyncSpy setup is brittle because the list-output response is tied to call
order rather than the command being invoked. Update the mocking in
services-sandbox.test.ts so the response for the forwarding list path is
selected by inspecting the spawnSync arguments inside the spy implementation,
and keep the existing behaviors for stopSandboxChannelsViaKubectl, the sandbox
exec fallback, and stopSandboxForwards. This should be anchored around the
spawnSyncSpy setup and the stopSandboxForwards-related expectations so unrelated
upstream spawnSync calls cannot shift the mock sequence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/tunnel/services.ts`:
- Around line 595-627: The success log in stopSandboxForwards currently counts
targeted ports rather than actual successful stops, so it can report releases
even when a forward stop fails. Update stopSandboxForwards to inspect each
spawnSync(openshell, ["forward", "stop", ...]) result, count only successful
stops, and log that count instead of ports.length. Keep the existing behavior
around resolveOpenshell, getOccupiedPorts, and the final info message, but base
the message on verified stop outcomes.

---

Nitpick comments:
In `@src/lib/tunnel/services-sandbox.test.ts`:
- Line 245: Update the test title in services-sandbox.test.ts so it follows the
repo’s test naming convention by ending with the local issue reference suffix in
the form (`#1234`). Keep the title behavior-oriented, and apply the change to the
existing it(...) block for the deprecated full stop case.
- Around line 247-259: The test’s spawnSyncSpy setup is brittle because the
list-output response is tied to call order rather than the command being
invoked. Update the mocking in services-sandbox.test.ts so the response for the
forwarding list path is selected by inspecting the spawnSync arguments inside
the spy implementation, and keep the existing behaviors for
stopSandboxChannelsViaKubectl, the sandbox exec fallback, and
stopSandboxForwards. This should be anchored around the spawnSyncSpy setup and
the stopSandboxForwards-related expectations so unrelated upstream spawnSync
calls cannot shift the mock sequence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 99cc10f1-2223-4d40-b8f2-fb748f231427

📥 Commits

Reviewing files that changed from the base of the PR and between 5a6f489 and bcb4a64.

📒 Files selected for processing (2)
  • src/lib/tunnel/services-sandbox.test.ts
  • src/lib/tunnel/services.ts

Comment on lines +595 to +627
function stopSandboxForwards(sandboxName: string): void {
const openshell = resolveOpenshell();
if (!openshell) {
warn("openshell not found — cannot release managed host forwards.");
return;
}

const listResult = spawnSync(openshell, ["forward", "list"], {
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
timeout: OPENSHELL_PROBE_TIMEOUT_MS,
});
if (listResult.status !== 0) {
warn("Could not list OpenShell forwards — managed host forwards may still be running.");
return;
}

const output = listResult.stdout ?? "";
const ports = [...getOccupiedPorts(output).entries()]
.filter(([, owner]) => owner === sandboxName)
.map(([port]) => port);
for (const port of ports) {
spawnSync(openshell, ["forward", "stop", port, sandboxName], {
encoding: "utf-8",
stdio: "ignore",
timeout: OPENSHELL_OPERATION_TIMEOUT_MS,
});
}
if (ports.length > 0) {
info(`Released ${String(ports.length)} managed host forward(s) for sandbox ${sandboxName}.`);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Success count doesn't verify actual forward stop outcome.

ports.length reflects how many ports were targeted, not how many stops actually succeeded — the spawnSync result for each forward stop call is discarded. If a stop fails, the log still reports Released N managed host forward(s), which is misleading precisely for the scenario this PR is fixing (confirming that a forward like port 8642 was actually released).

🐛 Proposed fix to track actual successes
-  for (const port of ports) {
-    spawnSync(openshell, ["forward", "stop", port, sandboxName], {
-      encoding: "utf-8",
-      stdio: "ignore",
-      timeout: OPENSHELL_OPERATION_TIMEOUT_MS,
-    });
-  }
-  if (ports.length > 0) {
-    info(`Released ${String(ports.length)} managed host forward(s) for sandbox ${sandboxName}.`);
-  }
+  let released = 0;
+  for (const port of ports) {
+    const stopResult = spawnSync(openshell, ["forward", "stop", port, sandboxName], {
+      encoding: "utf-8",
+      stdio: "ignore",
+      timeout: OPENSHELL_OPERATION_TIMEOUT_MS,
+    });
+    if (stopResult.status === 0) released += 1;
+  }
+  if (released > 0) {
+    info(`Released ${String(released)} managed host forward(s) for sandbox ${sandboxName}.`);
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function stopSandboxForwards(sandboxName: string): void {
const openshell = resolveOpenshell();
if (!openshell) {
warn("openshell not found — cannot release managed host forwards.");
return;
}
const listResult = spawnSync(openshell, ["forward", "list"], {
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
timeout: OPENSHELL_PROBE_TIMEOUT_MS,
});
if (listResult.status !== 0) {
warn("Could not list OpenShell forwards — managed host forwards may still be running.");
return;
}
const output = listResult.stdout ?? "";
const ports = [...getOccupiedPorts(output).entries()]
.filter(([, owner]) => owner === sandboxName)
.map(([port]) => port);
for (const port of ports) {
spawnSync(openshell, ["forward", "stop", port, sandboxName], {
encoding: "utf-8",
stdio: "ignore",
timeout: OPENSHELL_OPERATION_TIMEOUT_MS,
});
}
if (ports.length > 0) {
info(`Released ${String(ports.length)} managed host forward(s) for sandbox ${sandboxName}.`);
}
}
function stopSandboxForwards(sandboxName: string): void {
const openshell = resolveOpenshell();
if (!openshell) {
warn("openshell not found — cannot release managed host forwards.");
return;
}
const listResult = spawnSync(openshell, ["forward", "list"], {
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
timeout: OPENSHELL_PROBE_TIMEOUT_MS,
});
if (listResult.status !== 0) {
warn("Could not list OpenShell forwards — managed host forwards may still be running.");
return;
}
const output = listResult.stdout ?? "";
const ports = [...getOccupiedPorts(output).entries()]
.filter(([, owner]) => owner === sandboxName)
.map(([port]) => port);
let released = 0;
for (const port of ports) {
const stopResult = spawnSync(openshell, ["forward", "stop", port, sandboxName], {
encoding: "utf-8",
stdio: "ignore",
timeout: OPENSHELL_OPERATION_TIMEOUT_MS,
});
if (stopResult.status === 0) released += 1;
}
if (released > 0) {
info(`Released ${String(released)} managed host forward(s) for sandbox ${sandboxName}.`);
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/tunnel/services.ts` around lines 595 - 627, The success log in
stopSandboxForwards currently counts targeted ports rather than actual
successful stops, so it can report releases even when a forward stop fails.
Update stopSandboxForwards to inspect each spawnSync(openshell, ["forward",
"stop", ...]) result, count only successful stops, and log that count instead of
ports.length. Keep the existing behavior around resolveOpenshell,
getOccupiedPorts, and the final info message, but base the message on verified
stop outcomes.

@cv cv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The forward cleanup currently reports every attempted stop as released even when openshell forward stop fails, times out, or cannot spawn. stopSandboxForwards discards each spawnSync result and logs the selected port count unconditionally, so the linked port-leak bug can persist behind a success message. Please inspect each stop result, warn with the affected sandbox/port on failure, count only confirmed successes, and add a failed-stop/timeout regression that proves the command does not claim the forward was released. The existing happy-path test defaults all stop calls to status 0 and does not exercise this behavior.

@wscurran wscurran added v0.0.77 area: cli Command line interface, flags, terminal UX, or output area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery bug-fix PR fixes a bug or regression integration: hermes Hermes integration behavior and removed v0.0.76 labels Jul 7, 2026
@wscurran

wscurran commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

✨ Thanks for the fix, Chengjie. The targeted forward release and product-name cleanup look correct, and the regression coverage for port 8642 and multi-sandbox preservation gives good confidence. Ready for maintainer review.


Related open issues:


Related open issues:

@chengjiew chengjiew closed this Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cli Command line interface, flags, terminal UX, or output area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery bug-fix PR fixes a bug or regression integration: hermes Hermes integration behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[macOS][CLI&UX] nemohermes stop does not release port 8642 — SSH port-forward process survives after deprecated full stop

3 participants