Skip to content

fix: detect sandbox context in status command - #80

Closed
WuKongAI-CMU wants to merge 1 commit into
NVIDIA:mainfrom
WuKongAI-CMU:fix/status-reflects-real-state
Closed

fix: detect sandbox context in status command#80
WuKongAI-CMU wants to merge 1 commit into
NVIDIA:mainfrom
WuKongAI-CMU:fix/status-reflects-real-state

Conversation

@WuKongAI-CMU

Copy link
Copy Markdown
Contributor

Summary

Fixes #25openclaw nemoclaw status now correctly reports "running" when executed inside the sandbox.

Root cause: getSandboxStatus() shells out to openshell sandbox status <name> --json, but inside the sandbox the openshell CLI cannot query its own container. The catch block silently returned running: false.

Fix:

  • Add isInsideSandbox() detection (checks for /sandbox directory or OPENSHELL_SANDBOX=1 env var)
  • When inside a sandbox, skip the openshell query and report running: true directly — the code executing is proof the sandbox is alive
  • Outside the sandbox, behavior is unchanged

Test plan

  • TypeScript builds clean
  • Existing tests pass (19/19)
  • Manual: run openclaw nemoclaw status inside sandbox → should show "Status: running"
  • Manual: run openclaw nemoclaw status on host with sandbox stopped → should show "Status: not running"

🤖 Generated with Claude Code

When `openclaw nemoclaw status` runs inside the sandbox, the
openshell CLI cannot query its own container state, so
getSandboxStatus() always caught an error and reported "not running".

Now the status command detects the sandbox context by checking for
/sandbox or the OPENSHELL_SANDBOX env var. When inside a sandbox,
it reports "running" directly — the fact that the code is executing
proves the sandbox is alive.

Closes NVIDIA#25

Signed-off-by: peteryuqin <peter.yuqin@gmail.com>
@WuKongAI-CMU

Copy link
Copy Markdown
Contributor Author

Closing to reduce my open PR count below the repo policy limit and refocus on a smaller set of higher-signal changes. I can revive this branch later if it becomes the right path again.

mafueee pushed a commit to mafueee/NemoClaw that referenced this pull request Mar 28, 2026
… logs (!55)

Closes NVIDIA#78

## What You Can Do

### Live policy updates
Update a running sandbox's network policy without recreating it:
```bash
nav sandbox policy set <sandbox> --policy new-policy.yaml --wait --timeout 60
```
The sandbox hot-reloads the policy within 30s (configurable). On failure, the previous policy stays active (last-known-good).

Idempotent — submitting the same policy twice returns the existing version:
```
✓ Policy version 3 submitted (hash: a1b2c3d4e5f6)
$ nav sandbox policy set test --policy same.yaml
· Policy unchanged (version 3, hash: a1b2c3d4e5f6)
```

### Policy history & inspection
```bash
nav sandbox policy list <sandbox>          # version history with status
nav sandbox policy get <sandbox>           # current policy metadata
nav sandbox policy get <sandbox> --full    # print full policy as YAML
nav sandbox policy get <sandbox> --rev 2 --full  # specific revision as YAML
```

### Sandbox logs
Stream logs from both the gateway and sandbox supervisor in one view:
```bash
nav sandbox logs <sandbox>                      # one-shot, last 2000 lines
nav sandbox logs <sandbox> --tail               # live streaming
nav sandbox logs <sandbox> --source sandbox     # supervisor logs only
nav sandbox logs <sandbox> --source gateway     # gateway logs only
nav sandbox logs <sandbox> --level warn         # warnings and errors only
nav sandbox logs <sandbox> --since 5m           # last 5 minutes
nav sandbox logs <sandbox> --tail --source sandbox --level info
```

Each log line is tagged with its source and includes structured fields:
```
[1772055394.673] [sandbox] [INFO ] [navigator_sandbox::proxy] CONNECT action=allow dst_host=api.anthropic.com dst_port=443 policy=claude_code
[1772055061.005] [gateway] [INFO ] [navigator_server::grpc] GetSandboxPolicy served from policy history
```

---

## Implementation

### Proto changes
- 7 new RPCs: `UpdateSandboxPolicy`, `GetSandboxPolicyStatus`, `ListSandboxPolicies`, `ReportPolicyStatus`, `GetSandboxLogs`, `PushSandboxLogs` (client-streaming)
- `SandboxLogLine`: added `source` (gateway/sandbox), `fields` (structured key-value map)
- `WatchSandboxRequest`: added `log_since_ms`, `log_sources`, `log_min_level`
- `GetSandboxLogsRequest`: added `sources`, `min_level`
- `PolicyStatus` enum, `SandboxPolicyRevision` message
- `Sandbox.current_policy_version` field

### Server
- **Policy persistence**: New `sandbox_policies` table (SQLite + Postgres) with per-sandbox monotonic versions, status tracking, and policy hash
- **UpdateSandboxPolicy**: Validates static field immutability (filesystem/landlock/process), network mode consistency (Block↔Proxy), deterministic hash comparison for idempotent updates
- **Lazy backfill**: First `GetSandboxPolicy` call creates version 1 from `spec.policy` for existing sandboxes
- **Log broker**: `TracingLogBus::publish_external()` injects sandbox-pushed logs into the same broadcast channel + tail buffer (2000 lines). Server forces `source="sandbox"` and `sandbox_id` on all pushed logs
- **Source/level filtering**: Applied server-side in both `GetSandboxLogs` and `WatchSandbox` streams
- **Version supersession**: When a new version is loaded, all older pending+loaded versions are marked superseded

### Sandbox
- **`OpaEngine::reload_from_proto()`**: Full `from_proto()` pipeline (L7 validation, access preset expansion) with atomic engine swap. On failure, previous engine untouched (LKG)
- **Policy poll loop**: Background task polls every 30s (configurable via `NAVIGATOR_POLICY_POLL_INTERVAL_SECS`), reports status via `ReportPolicyStatus` RPC
- **`LogPushLayer`**: Tracing layer captures events at INFO+ (configurable via `NAVIGATOR_LOG_PUSH_LEVEL`), sends structured fields via `PushSandboxLogs` client-streaming RPC. Background task batches 50 lines / flushes every 500ms. Best-effort (drops on full channel, never blocks)
- **`CachedNavigatorClient`**: Persistent mTLS channel for both policy polling and log push

### Database migration
- `002_create_sandbox_policies.sql` (SQLite + Postgres)

## Tests
- **Unit**: 8 policy persistence tests (put/get/list/status/supersede/isolation)
- **Integration**: 4 test files updated with new RPC stubs
- **E2E**: `test_live_policy_update_and_logs` — full lifecycle: create → set same (unchanged) → push new → wait for load → verify connectivity → push same (unchanged) → verify history → fetch logs

## Documentation
- `architecture/sandbox.md`: Log streaming architecture, LogPushLayer, push task, server broker, source tagging, structured fields, CLI filtering, failure modes
- `architecture/security-policy.md`: Live update semantics, deterministic hashing, CLI filter flags, policy inspection
- `architecture/plans/issue-78-sandbox-log-streaming.md`: Design plan for log streaming

## Security
- Trust boundary documented: shared mTLS cert model (per-sandbox auth tracked in NVIDIA#80)
- Server forces `source="sandbox"` and `sandbox_id` on pushed logs (can't impersonate gateway or other sandboxes)
- Per-batch line cap (100) prevents flooding
mafueee pushed a commit to mafueee/NemoClaw that referenced this pull request Mar 28, 2026
* feat(tui): add port forwarding support to Gator (NVIDIA#80)

Extract forward PID management, resolve_ssh_gateway, and shell_escape from
navigator-cli into navigator-core::forward as a shared module. Add a Ports
field to the TUI create sandbox modal, a NOTES column to the sandbox table,
and a Forwards row to the sandbox detail view. On creation with ports, Gator
polls for Ready state then spawns background SSH tunnels. Forward cleanup
runs automatically on sandbox delete.

Closes NVIDIA#80

* fix(tui): fix Ports field invisible due to modal height overflow

The modal height calculation used content_height + 3 but block chrome
(borders + padding) is 4 rows, not 3. Combined with the 3-row Ports
field layout, the input line was clipped to zero height on standard
24-row terminals. Switch Ports to a compact single-line inline layout
and fix the chrome arithmetic.

* wip: BYOC example with port forwarding and TUI command field fix

- Move examples/bring-your-own-container.md into its own directory
- Add example Dockerfile and app.py (Python REST API with /hello endpoint)
- Rewrite README with full CLI and TUI port-forward workflows
- Change TUI Command field default from /bin/bash to empty so custom
  image entrypoints run without manual clearing

* feat(tui): wire up Command field to SSH exec after sandbox creation

When a command is specified in the create sandbox modal, the TUI now
waits for the sandbox to reach Ready (with the pacman animation),
starts any port forwards, then suspends the TUI and executes the
command via SSH — matching the CLI's post-creation flow.

- Refactor spawn_create_sandbox to poll for Ready inline when ports
  or command are set, keeping the animation visible throughout
- Add start_port_forwards() helper called within the create task
- Add handle_exec_command() to suspend TUI and run SSH exec
- Remove unused ForwardResult event variant (forwards are now started
  within the create task, not as a separate event)

* fix(tui): fix command exec, forward timeouts, and create modal UX

- Fix handle_exec_command to suspend TUI and run SSH attached (matching
  CLI behavior) so the process stays alive for the session duration
- Fix shell_escape double-quoting: escape each word individually
- Add ConnectTimeout and 20s spawn timeout to forward SSH so a stalled
  auth doesn't freeze the create flow forever
- Remove dead ForwardResult event variant
- Add cluster_name to start_port_forwards for ProxyCommand
- Add spacer between Providers and Ports in create modal
- Fix Command placeholder: 'runs /bin/bash if empty' (not image entrypoint)
- Update BYOC README: document that CMD is replaced by supervisor,
  command must be passed explicitly, remove TUI-specific sections

* fix(sandbox): demote Landlock fallback log from warn to debug

The Landlock filesystem sandbox emits a noisy warning when a policy
path does not exist (e.g. /app in custom images). Since BestEffort
mode intentionally continues without Landlock, demote to debug.

* docs: fix BYOC Dockerfile comments for supervisor CMD override

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
@wscurran wscurran added the bug-fix PR fixes a bug or regression label Jun 8, 2026
cv added a commit that referenced this pull request Jul 23, 2026
## Summary

Add one explicit, non-blocking staging Brev Launchable E2E lane targeted
for `.92`. The lane binds an exact candidate SHA to the exact
`nemoclaw-image` producer run, verifies the guest's provisioned SHA and
clean NemoClaw checkout after boot, reuses the existing full E2E in
preinstalled mode, and verifies workspace deletion. It does not gate
production Launchable promotion or claim immutable cloud image-ID
qualification.

## Related Issue

Part of #6943.

## Changes

- Add one explicit-only job to the existing E2E workflow; no standalone
workflow or controller.
- Dispatch `brevdev/nemoclaw-image` workflow #80 and validate that its
receipt binds the exact producer run to the candidate SHA.
- Deploy the standing staging Launchable, then verify
`/etc/nemoclaw/provision.json`, the checkout HEAD, and a clean working
tree all match the exact candidate.
- Run the existing full E2E with a small `preinstalled-launchable` setup
branch.
- Always attempt deletion and require two absent observations before the
lane can pass.
- Upload only `lane.log`, `qualification.json`, `full-e2e.log`, and
`cleanup.json`.
- Keep activation disabled until the protected environment, credentials,
standing Launchable, and Brev ownership are configured.

The implementation is 628 added lines: workflow 67, runner 227, existing
full-E2E adaptation 43, new lifecycle tests 225, and narrow
existing-validator/test changes 66.

## Type of Change

- [x] 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

- [x] Tests added or updated for changed behavior
- [ ] Existing tests cover changed behavior — justification:
- [ ] Tests not applicable — justification:
- [ ] Docs updated for user-facing behavior changes
- [x] Docs not applicable — justification: Maintainer-only E2E workflow;
the public Launchable flow and CLI are unchanged, and `e2e.yaml` remains
the release-E2E source of truth.
- [x] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [x] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification: nine-category PASS and
exact-head refresh, no findings:
#7270 (comment),
#7270 (comment)
- [ ] Non-success, skipped, or missing CI check accepted by maintainer —
check name, approval link, and follow-up issue:

## Documentation Writer Review

- [x] Documentation writer subagent reviewed the completed
implementation
- Result: `no-docs-needed`
- Evidence: The change adds a protected, explicit staging-only
qualification lane without changing the production Launchable, CLI,
installation, or supported user workflow.
- Agent: Codex Desktop
- PR: #7270
<!-- docs-review-head-sha: d0c7ada -->
<!-- docs-review-agents-blob-sha: 9c9b36d -->

## DGX Station Hardware Evidence

- [ ] Tested on DGX Station
- Tested commit: not applicable
- Station profile/scenario: not applicable
- Result: not applicable
- Supporting evidence: not applicable

## Verification

- [x] PR description includes a `Signed-off-by:` line and every commit
appears as `Verified` in GitHub
- [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or
`npm run check:diff` passed when hooks were skipped or unavailable
- [x] Targeted behavior tests pass for the current change set, or tests
are marked not applicable above — `4/4` focused lifecycle tests, `79/79`
relevant workflow-boundary tests, `10/10` Docker-auth boundary tests,
and the exact affected E2E-support shard (`154/154`) passed.
- [x] Applicable broad gate passed — Not applicable: this is one opt-in
lane; repository hooks and the targeted integration/E2E-support projects
cover the changed surfaces.
- [x] Quality Gates section completed with required justifications or
waivers
- [x] No secrets, API keys, or credentials committed
- [ ] `npm run docs` builds without warnings (doc changes only)
- [ ] 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)

---
Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>

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

* **New Features**
* Added a gated staging “Brev Launchable” end-to-end lane that builds a
handoff image, verifies produced manifests/immutables, launches a Brev
workspace, runs the full suite on a preinstalled environment, and
records qualification results.
* Enabled automatic staging evidence uploads (qualification, logs,
cleanup) for the lane.
* **Tests**
* Added a stubbed E2E suite covering the lane’s success path,
SHA/receipt mismatch handling, full E2E failure behavior, and workspace
cleanup verification.
* Extended live full E2E with a preinstalled-launchable setup mode and
adjusted related assertions.
* **Bug Fixes**
* Strengthened CI workflow-boundary checks for trusted checkout,
explicit artifact upload behavior, and “no-image” coverage rules.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Co-authored-by: Carlos Villela <cvillela@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug-fix PR fixes a bug or regression

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix openclaw nemoclaw status so plugin status reflects real sandbox state instead of always reporting "not running"

2 participants