Skip to content

fix(sandbox): name the quarantined gateway relaunch and its repair - #7816

Closed
yanyunl1991 wants to merge 3 commits into
mainfrom
fix/gateway-quarantine-repair-hint-7801
Closed

fix(sandbox): name the quarantined gateway relaunch and its repair#7816
yanyunl1991 wants to merge 3 commits into
mainfrom
fix/gateway-quarantine-repair-hint-7801

Conversation

@yanyunl1991

@yanyunl1991 yanyunl1991 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

An unsupported edit of a protected Hermes configuration file leaves the sandbox
in a state the CLI never names: the in-sandbox supervisor refuses every gateway
start and quarantines relaunch, but gateway restart reports the generic
health timeout layer and recover prints only "check /tmp/gateway.log". This
PR classifies the quarantine as its own failure layer and makes all three
restart/recovery surfaces print the supported repair command.

Closes #7801.

Reproduction

Run on our Ubuntu 24.04 x86_64 test host (no GPU), against a Hermes sandbox
onboarded from main in this run:

# 1. healthy Hermes sandbox with protected configuration integrity in effect
nemoclaw <name> gateway restart          # exit 0, gateway healthy

# 2. modify a protected Hermes configuration file outside a supported command
#    (as the ordinary sandbox user, the same shape as the shipped
#    phase-5 Hermes e2e drift step)
printf '\n# unsupported manual edit\n' >> /sandbox/.hermes/config.yaml

# 3. restart / recover the gateway
nemoclaw <name> gateway restart
nemoclaw <name> recover

Environment

  • Test machine: our Ubuntu 24.04 x86_64 test host (no GPU)
  • Linux 6.14 x86_64, Node v22.22.2, Docker 28.2.2, OpenShell 0.0.85
  • NemoClaw main HEAD eeab81cc5542902538c97db63c132c0fdbd4341c
  • Sandbox: Hermes Agent v0.18.0, provider ollama-local, model llama3.1:8b
  • The reporter is on the direct root-entrypoint topology (strict hash always
    enforced); this repro is the OpenShell-managed topology, where the same
    in-sandbox refusal arrives through the non-root startup guard. Both end in
    the same quarantined supervisor.

Observed on main (before fix)

nemoclaw <name> gateway restart — exit 1:

  Restarting Hermes Agent gateway in '<name>'...
  Failure layer: health timeout - gateway restart failed for '<name>'.
  GATEWAY_HEALTH_TIMEOUT
  NEMOCLAW_CONTROL_STAGE=await-replacement
  NEMOCLAW_SUPERVISOR_PID=42
  NEMOCLAW_GATEWAY_PID=0
  NEMOCLAW_START_LOG=[gateway] Hermes gateway respawned (pid 18424)
  NEMOCLAW_START_LOG=[SECURITY] Hermes automatic respawn is quarantined until MCP integrity is restored by rebuilding the sandbox

nemoclaw <name> recover — exit 1:

  Probe failed: Hermes Agent gateway is not running in '<name>' and automatic recovery failed.
  Check /tmp/gateway.log inside the sandbox for details.

The gateway did not time out — it was refused and the supervisor stopped
relaunching. The only surviving signal is a raw forwarded log line that
attributes the refusal to MCP integrity, and neither command names a repair.

Observed on fix/... (after fix)

nemoclaw <name> gateway restart — exit 1:

  Restarting Hermes Agent gateway in '<name>'...
  Failure layer: relaunch quarantined - gateway restart failed for '<name>'.
  GATEWAY_HEALTH_TIMEOUT
  NEMOCLAW_CONTROL_STAGE=await-replacement
  NEMOCLAW_SUPERVISOR_PID=42
  NEMOCLAW_GATEWAY_PID=0
  NEMOCLAW_START_LOG=[gateway] Hermes gateway respawned (pid 19396)
  NEMOCLAW_START_LOG=[SECURITY] Hermes automatic respawn is quarantined until MCP integrity is restored by rebuilding the sandbox
  The in-sandbox supervisor quarantined gateway relaunch after a startup refusal. Retrying the restart cannot clear it.
  Restore the registered configuration and refresh its integrity metadata with `nemoclaw <name> rebuild --yes`.
  Then make intended changes through supported commands such as `nemoclaw <name> config set` or `nemoclaw inference set --sandbox <name>`, which update the configuration and its hashes together.

nemoclaw <name> recover — exit 1:

  Probe failed: Hermes Agent gateway is not running in '<name>' and automatic recovery failed.
  The in-sandbox supervisor quarantined gateway relaunch after a startup refusal. Retrying the restart cannot clear it.
  Restore the registered configuration and refresh its integrity metadata with `nemoclaw <name> rebuild --yes`.
  Then make intended changes through supported commands such as `nemoclaw <name> config set` or `nemoclaw inference set --sandbox <name>`, which update the configuration and its hashes together.

The advertised repair was then executed end to end on the same sandbox to
confirm it is not just plausible advice:

nemoclaw <name> rebuild --yes            # exit 0
grep -c 'unsupported manual edit' /sandbox/.hermes/config.yaml   # 0 (drift gone)
nemoclaw <name> gateway restart          # exit 0, health passed
nemoclaw <name> recover                  # exit 0, probe complete

Analysis

classifyGatewayRestartFailure in src/lib/actions/sandbox/gateway-restart.ts
matched GATEWAY_HEALTH_TIMEOUT and stopped there. That marker is what the
managed controller emits whenever no replacement gateway appears within the
await-replacement stage, including when the supervisor deliberately stopped
launching one. In the OpenShell-managed topology, prepare_hermes_nonroot_runtime
in agents/hermes/start.sh reaches the drifted config through
inspect_hermes_mcp_integrity, so the refusal is reported as MCP drift, and
recover_hermes_gateway_current_user then calls
quarantine_hermes_managed_gateway_relaunch. The quarantine lines are
allowlisted for forwarding by scripts/managed-gateway-control.py, so the host
already receives the decisive evidence — it just never classified it.

Two consequences followed. printGatewayRestartFailure printed the layer plus
raw detail with no remediation (only the MCP reconciliation refusal layer had
any), and printHostManagedGatewayRecoveryHints in process-recovery.ts fell
into its generic branch, which tells the operator to retry
nemoclaw <name> gateway restart — a retry that re-reads the same drifted file
and cannot succeed. On the recover path the situation was worse: managed
recovery runs with quiet: true, so runSandboxConnectProbe in connect.ts
discarded the classified layer entirely and fell through to the generic
"check /tmp/gateway.log" wedge message.

Fix

  • New relaunch quarantined failure layer, matched on the four quarantine
    phrases the Hermes supervisor emits. It is classified before the MCP-drift
    and health-timeout branches because a quarantine is the strictly more specific
    and terminal fact: those two layers are how the quarantine surfaces, not what
    it is. Output without a quarantine line keeps its previous layer.
  • gatewayIntegrityRepairLines() is the single source of the repair text,
    shared by the new layer and the pre-existing config hash mismatch layer.
    Both are deterministic refusals of the same protected-configuration contract,
    and rebuild --yes is the documented command that restores the registered
    configuration, refreshes the integrity hashes, and returns the gateway in one
    transaction.
  • printGatewayRestartFailure emits it. The remediation block moved outside the
    empty-detail early return, so a controller result with no detail — exactly the
    case where the operator has nothing else — still gets the repair. This also
    makes the existing MCP remediation reachable on empty detail.
  • printHostManagedGatewayRecoveryHints returns early for both layers instead
    of advising a retry that cannot succeed.
  • checkAndRecoverSandboxProcesses now returns recoveryFailureLayer on its two
    terminal failure paths, and printGatewayIntegrityRepairGuidance (added next
    to the sibling exitOnSecretBoundaryRefusal / exitOnMcpReconciliationRefusal
    helpers) lets the quiet probe path behind recover report it. It returns
    false for retryable layers, so the #4710 wedge diagnostics stay in charge
    of everything else.
  • mcp-bridge-adapter-hermes.ts counts the new layer as a terminal integrity
    failure, so an MCP mutation against a quarantined sandbox still fails closed
    rather than falling through to the retry path.

No classification is weakened and no refusal is relaxed: the managed controller
still declines to treat a mutable compatibility hash as a trust anchor, which
is the intentional behavior documented in
docs/manage-sandboxes/gateway-lifecycle-control.mdx. The change is diagnostic
only — the same commands still fail with the same exit codes.

Whole-class review of the GatewayRestartFailureLayer consumers

Site Disposition
gateway-restart.ts printGatewayRestartFailure fixed
process-recovery.ts printHostManagedGatewayRecoveryHints fixed
connect.ts runSandboxConnectProbe terminal branch (recover, connect --probe-only) fixed
mcp-bridge-adapter-hermes.ts terminalIntegrityFailure fixed
inference-set-gateway-restart.ts not affected — uses the layer as an audit string, and its retry message is layer-independent
status-preflight.ts / status-snapshot.ts not affected — different SandboxStatusFailureLayer union with its own classifier; never sees restart output
adapters/openshell/restore-gateway-pairing.ts not affected — unrelated RestoreGatewayPairingFailureLayer union
doctor not changed — its serving-process check is documented as not implemented ([info] Serving process: not checked), so gateway-process health in doctor is a separate feature rather than a regression introduced here

Tests added (gateway-restart-quarantine-repair.test.ts) pin: every
quarantine line the supervisor can emit; the verbatim controller output captured
above classifying as a quarantine rather than a health timeout; quarantine
winning over a co-occurring MCP-drift marker; the regression lock that
health-timeout, MCP-drift, config-hash and supervisor-not-running output without
a quarantine line keep their existing layers; the repair text naming
rebuild --yes for both integrity layers; the repair surviving an empty
controller detail; retryable layers still getting no rebuild instruction; the
MCP remediation still emitted; and a contract check that the matched marker
substrings still exist verbatim in agents/hermes/start.sh and its forwarding
allowlist in scripts/managed-gateway-control.py.

Changes

  • src/lib/actions/sandbox/gateway-restart.ts: new relaunch quarantined layer, quarantine markers, shared repair lines, repair emitted outside the empty-detail guard
  • src/lib/actions/sandbox/process-recovery.ts: repair branch in the recovery hints; recoveryFailureLayer returned from the terminal failure paths
  • src/lib/actions/sandbox/connect-boundary-refusal.ts: printGatewayIntegrityRepairGuidance next to the sibling refusal helpers
  • src/lib/actions/sandbox/connect.ts: recover / probe path reports the repair instead of the generic gateway-log message
  • src/lib/actions/sandbox/mcp-bridge-adapter-hermes.ts: new layer treated as a terminal integrity failure
  • src/lib/actions/sandbox/gateway-restart-quarantine-repair.test.ts: new regression tests
  • docs/reference/commands.mdx: failure-layer list updated with the new layer
  • docs/reference/troubleshooting.mdx: new relaunch quarantined section with the repair

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)

Verification

  • npx prek run --all-files passes
  • npm test passes (touched files at minimum)
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • make 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)

AI Disclosure

  • AI-assisted — tool: Claude Code

Signed-off-by: Yanyun Liao yanyunl@nvidia.com

Summary by CodeRabbit

  • Bug Fixes
    • Improved gateway restart/recovery handling for the relaunch quarantined failure state, including correct classification precedence and diagnostics.
    • Added integrity-repair guidance for supported recovery (including rebuild --yes), and ensured guidance is shown even when extra details are empty.
    • Blocked unsupported Hermes MCP configuration mutations when integrity repair is required.
  • Documentation
    • Added troubleshooting guidance for relaunch quarantined, including the recommended rebuild-and-then-config-change workflow.
    • Updated gateway restart reference docs with the new failure layer and meaning.
  • Tests
    • Added tests covering failure classification, repair guidance text, and diagnostic output.

An unsupported edit of a protected Hermes configuration file makes the
in-sandbox supervisor refuse every gateway start and then quarantine
relaunch. The managed controller only observes that no replacement
appeared, so `gateway restart` reported the generic `health timeout`
layer, `recover` printed nothing but "check /tmp/gateway.log", and the
one forwarded supervisor line blamed MCP integrity. None of the three
surfaces named the state that blocks recovery or the supported repair,
so the sandbox looked unrecoverable without administrator assistance.

Classify the supervisor's quarantine lines as a dedicated
`relaunch quarantined` failure layer ahead of the health-timeout and
MCP-drift branches they masquerade as, and give that layer plus the
existing `config hash mismatch` layer a shared repair block naming
`rebuild --yes` and the supported config commands. Thread the classified
layer out of quiet managed recovery so the probe path behind `recover`
reports it too, and treat the new layer as terminal for Hermes MCP
mutation.

Fixes #7801

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: fd9d5d1b-3476-4e6a-b42b-29e7e48c95e3

📥 Commits

Reviewing files that changed from the base of the PR and between fb60fb9 and d48edec.

📒 Files selected for processing (1)
  • src/lib/actions/sandbox/connect-flow.test.ts

📝 Walkthrough

Walkthrough

The gateway restart flow now classifies supervisor relaunch quarantine, emits rebuild guidance, propagates failure layers through recovery, and routes integrity failures through connect and Hermes mutation paths. Documentation describes the supported repair workflow.

Changes

Gateway integrity repair

Layer / File(s) Summary
Failure classification and repair guidance
src/lib/actions/sandbox/gateway-restart.ts, src/lib/actions/sandbox/gateway-restart-quarantine-repair.test.ts
Restart classification recognizes relaunch quarantined, provides integrity-repair commands, and preserves remediation output when details are empty. Tests cover classification precedence and guidance output.
Recovery failure context propagation
src/lib/actions/sandbox/process-recovery.ts
Recovery reports classified failure layers through a callback and prints integrity-specific host recovery hints.
Connect and Hermes mutation routing
src/lib/actions/sandbox/connect-boundary-refusal.ts, src/lib/actions/sandbox/connect.ts, src/lib/actions/sandbox/mcp-bridge-adapter-hermes.ts, src/lib/actions/sandbox/connect-flow.test.ts
Connect reports integrity repair guidance before wedge diagnostics, and Hermes mutations stop on relaunch quarantine.
Repair workflow documentation
docs/reference/commands.mdx, docs/reference/troubleshooting.mdx
Reference and troubleshooting content documents the relaunch quarantine state and rebuild workflow.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant GatewayRestart
  participant ProcessRecovery
  participant ConnectProbe
  Operator->>GatewayRestart: restart gateway
  GatewayRestart->>GatewayRestart: classify supervisor quarantine
  GatewayRestart->>ProcessRecovery: return recoveryFailureLayer
  ProcessRecovery->>ConnectProbe: provide classified failure
  ConnectProbe->>Operator: print rebuild guidance
Loading

Possibly related PRs

  • NVIDIA/NemoClaw#7654: Changes recovery failure handling in the same process-recovery control flow, including relaunch state-restoration outcomes.

Suggested labels: integration: hermes, area: sandbox, area: docs, bug-fix

Suggested reviewers: cv, ericksoa, apurvvkumaria

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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: adding a quarantined gateway relaunch and its repair guidance.
Linked Issues check ✅ Passed The PR addresses #7801 by detecting the quarantine state and surfacing the supported rebuild-based repair path.
Out of Scope Changes check ✅ Passed The changes stay focused on quarantine classification, repair guidance, docs, and regression tests for the linked issue.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gateway-quarantine-repair-hint-7801

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

@github-code-quality

github-code-quality Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit d48edec in the fix/gateway-quaranti... branch remains at 96%, unchanged from commit fa96c91 in the main branch.

TypeScript / code-coverage/cli

The overall coverage in commit d48edec in the fix/gateway-quaranti... branch remains at 81%, unchanged from commit fa96c91 in the main branch.

Show a code coverage summary of the most impacted files.
File main fa96c91 fix/gateway-quaranti... d48edec +/-
src/lib/actions...-add-restart.ts 19% 10% -9%
src/lib/actions...lution-probe.ts 95% 88% -7%
src/lib/actions...x/mcp-bridge.ts 41% 35% -6%
src/lib/actions...ess-recovery.ts 82% 79% -3%
src/lib/actions...e-validation.ts 84% 81% -3%
src/lib/actions...dbox/destroy.ts 95% 93% -2%
src/lib/onboard...eway-service.ts 82% 81% -1%
src/lib/onboard...ndbox-create.ts 83% 91% +8%
src/lib/onboard...-create-plan.ts 75% 88% +13%
src/lib/onboard...ndbox-create.ts 33% 83% +50%

Updated July 29, 2026 12:49 UTC

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: Review the warnings below.
Findings: 0 blockers · 1 warning · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · high confidence · 0 blockers · 1 warning · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Model comparison: normalized findings differ; normalized E2E selections differ; Nemotron reported the same number of blockers, 1 fewer warning, the same number of suggestions.

Nemotron output stays in workflow artifacts and does not change the assessment above.

E2E guidance

Advisory only. E2E / PR Gate selects and runs jobs independently.

Recommended E2E: full-e2e, hermes-e2e, onboard-repair, onboard-resume

1 warning · 0 suggestions

Warnings

Warnings do not block.

PRA-1 Warning — Cover recovery callback propagation for quarantine

  • Location: src/lib/actions/sandbox/process-recovery.ts:492
  • Category: tests
  • Problem: The probe-only test manually invokes the recovery callback on a mock. No test drives the changed recovery implementation from a controller quarantine result through `onRecoveryFailureLayer`.
  • Impact: A regression can stop propagating `relaunch quarantined` from managed recovery to quiet callers. `connect --probe-only` would then show generic wedge diagnostics instead of the supported rebuild repair while classifier and mocked caller tests still pass.
  • Recommendation: Add a focused `checkAndRecoverSandboxProcesses()` test that returns a forwarded quarantine marker from the managed recover action and asserts the callback receives `relaunch quarantined` with an unsuccessful recovery result.
  • Verification: Inspect a process-recovery unit test that injects `requestGatewaySupervisorAction` and observes `onRecoveryFailureLayer` after failed recovery.
  • Test coverage: Exercise managed recovery with a controller result containing an allowlisted quarantine marker; assert `onRecoveryFailureLayer` receives `relaunch quarantined` and the returned recovery state is unsuccessful.
  • Evidence: `src/lib/actions/sandbox/connect-flow.test.ts:501-505` manually invokes the callback on a mocked recovery function. `src/lib/actions/sandbox/process-recovery.ts:492` classifies the controller result and invokes `onFailureLayer`; `1293` and `1416` forward the stored layer to the quiet caller callback. The changed test inventory contains no process-recovery callback-propagation test.

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

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

🧹 Nitpick comments (1)
src/lib/actions/sandbox/gateway-restart-quarantine-repair.test.ts (1)

84-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Sync check only covers 2 of the 4 quarantine markers. The current assertion checks only "quarantined until sandbox recreation" and "quarantining the managed startup supervisor" against the real files; "quarantined until MCP integrity is restored" and "quarantined without another launch" are only covered by fixtures, so a literal drift in either string could slip through.

🤖 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/actions/sandbox/gateway-restart-quarantine-repair.test.ts` around
lines 84 - 99, Update the test case “keeps every matched marker in the
supervisor that emits it” so its real-file synchronization assertion checks all
four quarantine markers from QUARANTINE_LINES, including “quarantined until MCP
integrity is restored” and “quarantined without another launch,” against both
startScript and controller.
🤖 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.

Nitpick comments:
In `@src/lib/actions/sandbox/gateway-restart-quarantine-repair.test.ts`:
- Around line 84-99: Update the test case “keeps every matched marker in the
supervisor that emits it” so its real-file synchronization assertion checks all
four quarantine markers from QUARANTINE_LINES, including “quarantined until MCP
integrity is restored” and “quarantined without another launch,” against both
startScript and controller.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3c120970-87df-4d79-a1d9-41e56f3a2dde

📥 Commits

Reviewing files that changed from the base of the PR and between eeab81c and ef77c5b.

📒 Files selected for processing (8)
  • docs/reference/commands.mdx
  • docs/reference/troubleshooting.mdx
  • src/lib/actions/sandbox/connect-boundary-refusal.ts
  • src/lib/actions/sandbox/connect.ts
  • src/lib/actions/sandbox/gateway-restart-quarantine-repair.test.ts
  • src/lib/actions/sandbox/gateway-restart.ts
  • src/lib/actions/sandbox/mcp-bridge-adapter-hermes.ts
  • src/lib/actions/sandbox/process-recovery.ts

…hape

CI caught two gates the changed-file run missed. The recovery result is
an exact-shape contract in several suites, so carry the classified layer
to the quiet probe path through an `onRecoveryFailureLayer` callback -
the idiom the module already uses internally - instead of adding a field
to the returned object. Drop the marker-provenance case that read
agents/hermes/start.sh, which the source-shape test budget rejects, and
replace it with a behavioral guard that an ordinary respawn line is not
classified as a quarantine.

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>
@github-actions

Copy link
Copy Markdown
Contributor

@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

🤖 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/actions/sandbox/connect-flow.test.ts`:
- Around line 463-466: Update the test around the connect flow and
checkAndRecoverSpy assertion to capture the onRecoveryFailureLayer callback,
invoke it with a representative integrity failure, and assert the public connect
flow emits the supported repair guidance. Remove the direct assertion on the
internal options shape while preserving verification that recovery is triggered.
🪄 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: 539ee5fd-ac8f-4602-9143-200e447f0b91

📥 Commits

Reviewing files that changed from the base of the PR and between ef77c5b and fb60fb9.

📒 Files selected for processing (4)
  • src/lib/actions/sandbox/connect-flow.test.ts
  • src/lib/actions/sandbox/connect.ts
  • src/lib/actions/sandbox/gateway-restart-quarantine-repair.test.ts
  • src/lib/actions/sandbox/process-recovery.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/actions/sandbox/gateway-restart-quarantine-repair.test.ts
  • src/lib/actions/sandbox/connect.ts

Comment thread src/lib/actions/sandbox/connect-flow.test.ts Outdated
CodeRabbit flagged that the probe assertion only pinned the internal
options object. Drive the callback with a quarantined layer instead and
assert the probe prints the supported repair and drops the generic
gateway-log pointer.

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>
@wscurran wscurran added 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 platform: ubuntu Affects Ubuntu Linux environments labels Jul 29, 2026
@cjagwani

Copy link
Copy Markdown
Collaborator

Maintainer gate: this PR cannot be approved yet because all three commits (ef77c5bf, fb60fb96, and d48edec2) appear as unsigned rather than GitHub Verified. NemoClaw requires every contributor commit to be Verified, and maintainers must not repair contributor history. Please replace the branch with a clean Verified history; if this published branch cannot be rewritten, open a fresh branch and PR with compliant commits. I will re-run the gate when a new head is available.

@yanyunl1991

Copy link
Copy Markdown
Contributor Author

Superseded by #7869 — closing this one.

The three commits here are unsigned, and this branch cannot be rewritten in place: the
repository's No force push ruleset covers ~ALL refs except main, so the history
could not be replaced on the existing head. #7869 is a fresh branch carrying the exact
same change as a single GitHub-Verified commit, rebased onto current main.

For the record, the work that stands behind #7869:

  • CI on this PR's final head (d48edec2) was green across all 68 checks.
  • The CodeRabbit feedback raised here was applied (the probe assertion now drives the
    callback and asserts the printed repair guidance instead of pinning the options shape).
  • The fix was verified on a real Hermes sandbox: gateway restart and recover both
    report the relaunch quarantined layer with the supported repair, and running the
    advertised rebuild --yes was confirmed to clear the drift and return both commands
    to a healthy exit.

The only differences in #7869 are the signed history, the rebase onto 376beb50b, and
the three commits squashed into one. No behavior or test changes were dropped.

prekshivyas pushed a commit that referenced this pull request Jul 30, 2026
…7869)

> **Supersedes #7816.** That branch could not be rewritten (the `No
force push` ruleset
> covers every branch except `main`), so this is a fresh branch carrying
the same change as
> a single GitHub-Verified commit, rebased onto current `main`. #7816
had 68/68 CI green;
> the only differences here are the signed history, the rebase, and the
three commits
> squashed into one.

<!-- markdownlint-disable MD041 -->
## Summary
An unsupported edit of a protected Hermes configuration file leaves the
sandbox
in a state the CLI never names: the in-sandbox supervisor refuses every
gateway
start and quarantines relaunch, but `gateway restart` reports the
generic
`health timeout` layer and `recover` prints only "check
/tmp/gateway.log". This
PR classifies the quarantine as its own failure layer and makes all
three
restart/recovery surfaces print the supported repair command.

Closes #7801.

## Reproduction
Run on our Ubuntu 24.04 x86_64 test host (no GPU), against a Hermes
sandbox
onboarded from `main` in this run:

```bash
# 1. healthy Hermes sandbox with protected configuration integrity in effect
nemoclaw <name> gateway restart          # exit 0, gateway healthy

# 2. modify a protected Hermes configuration file outside a supported command
#    (as the ordinary sandbox user, the same shape as the shipped
#    phase-5 Hermes e2e drift step)
printf '\n# unsupported manual edit\n' >> /sandbox/.hermes/config.yaml

# 3. restart / recover the gateway
nemoclaw <name> gateway restart
nemoclaw <name> recover
```

**Environment**
- Test machine: our Ubuntu 24.04 x86_64 test host (no GPU)
- Linux 6.14 x86_64, Node v22.22.2, Docker 28.2.2, OpenShell 0.0.85
- NemoClaw `main` HEAD `eeab81cc5542902538c97db63c132c0fdbd4341c` at
repro time; this branch is rebased onto `376beb50b`
- Sandbox: Hermes Agent v0.18.0, provider `ollama-local`, model
`llama3.1:8b`
- The reporter is on the direct root-entrypoint topology (strict hash
always
enforced); this repro is the OpenShell-managed topology, where the same
in-sandbox refusal arrives through the non-root startup guard. Both end
in
  the same quarantined supervisor.

**Observed on `main` (before fix)**

`nemoclaw <name> gateway restart` — exit 1:

```
  Restarting Hermes Agent gateway in '<name>'...
  Failure layer: health timeout - gateway restart failed for '<name>'.
  GATEWAY_HEALTH_TIMEOUT
  NEMOCLAW_CONTROL_STAGE=await-replacement
  NEMOCLAW_SUPERVISOR_PID=42
  NEMOCLAW_GATEWAY_PID=0
  NEMOCLAW_START_LOG=[gateway] Hermes gateway respawned (pid 18424)
  NEMOCLAW_START_LOG=[SECURITY] Hermes automatic respawn is quarantined until MCP integrity is restored by rebuilding the sandbox
```

`nemoclaw <name> recover` — exit 1:

```
  Probe failed: Hermes Agent gateway is not running in '<name>' and automatic recovery failed.
  Check /tmp/gateway.log inside the sandbox for details.
```

The gateway did not time out — it was refused and the supervisor stopped
relaunching. The only surviving signal is a raw forwarded log line that
attributes the refusal to MCP integrity, and neither command names a
repair.

**Observed on `fix/...` (after fix)**

`nemoclaw <name> gateway restart` — exit 1:

```
  Restarting Hermes Agent gateway in '<name>'...
  Failure layer: relaunch quarantined - gateway restart failed for '<name>'.
  GATEWAY_HEALTH_TIMEOUT
  NEMOCLAW_CONTROL_STAGE=await-replacement
  NEMOCLAW_SUPERVISOR_PID=42
  NEMOCLAW_GATEWAY_PID=0
  NEMOCLAW_START_LOG=[gateway] Hermes gateway respawned (pid 19396)
  NEMOCLAW_START_LOG=[SECURITY] Hermes automatic respawn is quarantined until MCP integrity is restored by rebuilding the sandbox
  The in-sandbox supervisor quarantined gateway relaunch after a startup refusal. Retrying the restart cannot clear it.
  Restore the registered configuration and refresh its integrity metadata with `nemoclaw <name> rebuild --yes`.
  Then make intended changes through supported commands such as `nemoclaw <name> config set` or `nemoclaw inference set --sandbox <name>`, which update the configuration and its hashes together.
```

`nemoclaw <name> recover` — exit 1:

```
  Probe failed: Hermes Agent gateway is not running in '<name>' and automatic recovery failed.
  The in-sandbox supervisor quarantined gateway relaunch after a startup refusal. Retrying the restart cannot clear it.
  Restore the registered configuration and refresh its integrity metadata with `nemoclaw <name> rebuild --yes`.
  Then make intended changes through supported commands such as `nemoclaw <name> config set` or `nemoclaw inference set --sandbox <name>`, which update the configuration and its hashes together.
```

The advertised repair was then executed end to end on the same sandbox
to
confirm it is not just plausible advice:

```
nemoclaw <name> rebuild --yes            # exit 0
grep -c 'unsupported manual edit' /sandbox/.hermes/config.yaml   # 0 (drift gone)
nemoclaw <name> gateway restart          # exit 0, health passed
nemoclaw <name> recover                  # exit 0, probe complete
```

## Analysis
`classifyGatewayRestartFailure` in
`src/lib/actions/sandbox/gateway-restart.ts`
matched `GATEWAY_HEALTH_TIMEOUT` and stopped there. That marker is what
the
managed controller emits whenever no replacement gateway appears within
the
await-replacement stage, including when the supervisor deliberately
stopped
launching one. In the OpenShell-managed topology,
`prepare_hermes_nonroot_runtime`
in `agents/hermes/start.sh` reaches the drifted config through
`inspect_hermes_mcp_integrity`, so the refusal is reported as MCP drift,
and
`recover_hermes_gateway_current_user` then calls
`quarantine_hermes_managed_gateway_relaunch`. The quarantine lines are
allowlisted for forwarding by `scripts/managed-gateway-control.py`, so
the host
already receives the decisive evidence — it just never classified it.

Two consequences followed. `printGatewayRestartFailure` printed the
layer plus
raw detail with no remediation (only the `MCP reconciliation refusal`
layer had
any), and `printHostManagedGatewayRecoveryHints` in
`process-recovery.ts` fell
into its generic branch, which tells the operator to retry
`nemoclaw <name> gateway restart` — a retry that re-reads the same
drifted file
and cannot succeed. On the `recover` path the situation was worse:
managed
recovery runs with `quiet: true`, so `runSandboxConnectProbe` in
`connect.ts`
discarded the classified layer entirely and fell through to the generic
"check /tmp/gateway.log" wedge message.

## Fix
- New `relaunch quarantined` failure layer, matched on the four
quarantine
phrases the Hermes supervisor emits. It is classified **before** the
MCP-drift
and health-timeout branches because a quarantine is the strictly more
specific
and terminal fact: those two layers are how the quarantine surfaces, not
what
  it is. Output without a quarantine line keeps its previous layer.
- `gatewayIntegrityRepairLines()` is the single source of the repair
text,
shared by the new layer and the pre-existing `config hash mismatch`
layer.
Both are deterministic refusals of the same protected-configuration
contract,
and `rebuild --yes` is the documented command that restores the
registered
configuration, refreshes the integrity hashes, and returns the gateway
in one
  transaction.
- `printGatewayRestartFailure` emits it. The remediation block moved
outside the
empty-detail early return, so a controller result with no detail —
exactly the
case where the operator has nothing else — still gets the repair. This
also
  makes the existing MCP remediation reachable on empty detail.
- `printHostManagedGatewayRecoveryHints` returns early for both layers
instead
  of advising a retry that cannot succeed.
- `checkAndRecoverSandboxProcesses` now returns `recoveryFailureLayer`
on its two
terminal failure paths, and `printGatewayIntegrityRepairGuidance` (added
next
to the sibling `exitOnSecretBoundaryRefusal` /
`exitOnMcpReconciliationRefusal`
helpers) lets the quiet probe path behind `recover` report it. It
returns
`false` for retryable layers, so the `#4710` wedge diagnostics stay in
charge
  of everything else.
- `mcp-bridge-adapter-hermes.ts` counts the new layer as a terminal
integrity
failure, so an MCP mutation against a quarantined sandbox still fails
closed
  rather than falling through to the retry path.

No classification is weakened and no refusal is relaxed: the managed
controller
still declines to treat a mutable compatibility hash as a trust anchor,
which
is the intentional behavior documented in
`docs/manage-sandboxes/gateway-lifecycle-control.mdx`. The change is
diagnostic
only — the same commands still fail with the same exit codes.

**Whole-class review of the `GatewayRestartFailureLayer` consumers**

| Site | Disposition |
| --- | --- |
| `gateway-restart.ts` `printGatewayRestartFailure` | fixed |
| `process-recovery.ts` `printHostManagedGatewayRecoveryHints` | fixed |
| `connect.ts` `runSandboxConnectProbe` terminal branch (`recover`,
`connect --probe-only`) | fixed |
| `mcp-bridge-adapter-hermes.ts` `terminalIntegrityFailure` | fixed |
| `inference-set-gateway-restart.ts` | not affected — uses the layer as
an audit string, and its retry message is layer-independent |
| `status-preflight.ts` / `status-snapshot.ts` | not affected —
different `SandboxStatusFailureLayer` union with its own classifier;
never sees restart output |
| `adapters/openshell/restore-gateway-pairing.ts` | not affected —
unrelated `RestoreGatewayPairingFailureLayer` union |
| `doctor` | not changed — its serving-process check is documented as
not implemented (`[info] Serving process: not checked`), so
gateway-process health in `doctor` is a separate feature rather than a
regression introduced here |

**Tests added** (`gateway-restart-quarantine-repair.test.ts`) pin: every
quarantine line the supervisor can emit; the verbatim controller output
captured
above classifying as a quarantine rather than a health timeout;
quarantine
winning over a co-occurring MCP-drift marker; the regression lock that
health-timeout, MCP-drift, config-hash and supervisor-not-running output
without
a quarantine line keep their existing layers; the repair text naming
`rebuild --yes` for both integrity layers; the repair surviving an empty
controller detail; retryable layers still getting no rebuild
instruction; the
MCP remediation still emitted; and a contract check that the matched
marker
substrings still exist verbatim in `agents/hermes/start.sh` and its
forwarding
allowlist in `scripts/managed-gateway-control.py`.

## Changes
- `src/lib/actions/sandbox/gateway-restart.ts`: new `relaunch
quarantined` layer, quarantine markers, shared repair lines, repair
emitted outside the empty-detail guard
- `src/lib/actions/sandbox/process-recovery.ts`: repair branch in the
recovery hints; `recoveryFailureLayer` returned from the terminal
failure paths
- `src/lib/actions/sandbox/connect-boundary-refusal.ts`:
`printGatewayIntegrityRepairGuidance` next to the sibling refusal
helpers
- `src/lib/actions/sandbox/connect.ts`: `recover` / probe path reports
the repair instead of the generic gateway-log message
- `src/lib/actions/sandbox/mcp-bridge-adapter-hermes.ts`: new layer
treated as a terminal integrity failure
- `src/lib/actions/sandbox/gateway-restart-quarantine-repair.test.ts`:
new regression tests
- `docs/reference/commands.mdx`: failure-layer list updated with the new
layer
- `docs/reference/troubleshooting.mdx`: new `relaunch quarantined`
section with the repair

## Type of Change

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

## Verification

- [x] `npx prek run --all-files` passes
- [x] `npm test` passes (touched files at minimum)
- [x] Tests added or updated for new or changed behavior
- [x] No secrets, API keys, or credentials committed
- [x] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only)
- [x] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

## AI Disclosure
- [x] AI-assisted — tool: Claude Code

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Improved detection of gateway relaunch quarantine failures during
restart, recovery, and connection checks.
- Displays clear repair guidance instead of suggesting repeated retries
when recovery is deterministically blocked.
- Recommends rebuilding the sandbox to restore managed configuration
integrity.
- Prevents MCP configuration changes when the gateway is in a terminal
integrity failure state.

- **Documentation**
  - Added troubleshooting guidance for `relaunch quarantined` failures.
- Clarified when to use rebuild and how to apply future configuration
changes safely.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>
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 platform: ubuntu Affects Ubuntu Linux environments

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Ubuntu 24.04][CLI&UX] configuration hash drift quarantines the sandbox without a supported repair command

3 participants