Skip to content

feat(channels): add host-side channels command + block sandbox-side mutation#2139

Merged
ericksoa merged 4 commits intomainfrom
fix/2097-channels-host-side
Apr 21, 2026
Merged

feat(channels): add host-side channels command + block sandbox-side mutation#2139
ericksoa merged 4 commits intomainfrom
fix/2097-channels-host-side

Conversation

@laitingsheng
Copy link
Copy Markdown
Contributor

@laitingsheng laitingsheng commented Apr 21, 2026

openclaw channels add|remove fails with EACCES inside the sandbox because /sandbox/.openclaw/openclaw.json is read-only (Landlock + DAC hardening), and nemoclaw stop only tears down auxiliary host services. That leaves no supported path to add or remove a messaging channel after onboarding short of rerunning nemoclaw onboard --resume and navigating the full TUI.

Add a surgical host-side verb and block the sandbox-side mutation with a clear redirect:

  • New nemoclaw <name> channels <list|add|remove> handlers that persist / clear tokens under ~/.nemoclaw/credentials.json and offer to rebuild the sandbox so the image reflects the change. Honour NEMOCLAW_NON_INTERACTIVE=1 by queuing the change and pointing at nemoclaw <name> rebuild for the follow-up.
  • Extend install_configure_guard in nemoclaw-start.sh to intercept openclaw channels <mutator> from inside the sandbox with an actionable error pointing at the host-side verbs. Read-only subcommands (list, help) fall through to the real binary.

Consolidate channel metadata into src/lib/sandbox-channels.ts as the single source of truth — setupMessagingChannels in onboard.ts now materialises its array view via listChannels() instead of carrying a duplicate inline definition.

Fixes #2097.

Summary

Related Issue

Changes

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
  • 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: Tinson Lai tinsonl@nvidia.com

Summary by CodeRabbit

  • New Features

    • Added host-side channel management: nemoclaw <name> channels list|add|remove with --dry-run. list is read-only; add persists tokens (idempotent), remove clears them. Slack requires both bot+app tokens. Rebuild offered or queued when NEMOCLAW_NON_INTERACTIVE=1.
  • Documentation

    • New command reference and troubleshooting explaining read-only sandboxes, why in-sandbox channel mutations fail, credential storage location, and host-side workflow.
  • Tests

    • Added tests covering channel utilities and the sandbox-start guard behavior.

…utation

`openclaw channels add|remove` fails with EACCES inside the sandbox
because `/sandbox/.openclaw/openclaw.json` is read-only (Landlock + DAC
hardening), and `nemoclaw stop` only tears down auxiliary host services.
That leaves no supported path to add or remove a messaging channel after
onboarding short of rerunning `nemoclaw onboard --resume` and navigating
the full TUI.

Add a surgical host-side verb and block the sandbox-side mutation with a
clear redirect:

- New `nemoclaw <name> channels <list|add|remove>` handlers that
  persist / clear tokens under `~/.nemoclaw/credentials.json` and
  offer to rebuild the sandbox so the image reflects the change.
  Honour `NEMOCLAW_NON_INTERACTIVE=1` by queuing the change and
  pointing at `nemoclaw <name> rebuild` for the follow-up.
- Extend `install_configure_guard` in `nemoclaw-start.sh` to intercept
  `openclaw channels <mutator>` from inside the sandbox with an actionable
  error pointing at the host-side verbs. Read-only subcommands
  (`list`, help) fall through to the real binary.

Consolidate channel metadata into `src/lib/sandbox-channels.ts` as the
single source of truth — `setupMessagingChannels` in `onboard.ts` now
materialises its array view via `listChannels()` instead of carrying a
duplicate inline definition.

Fixes #2097.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 148f7127-075d-4220-9c54-70b03e837402

📥 Commits

Reviewing files that changed from the base of the PR and between f7331ff and 0089cf1.

📒 Files selected for processing (1)
  • src/nemoclaw.ts

📝 Walkthrough

Walkthrough

Adds host-side messaging-channel management: new channels CLI with list, add, remove; a sandbox-channel library for channel metadata and credential persistence; onboarding updated to use dynamic channel list; sandbox start guard blocks in-sandbox channel mutations; docs and tests added.

Changes

Cohort / File(s) Summary
Channel Management Library
src/lib/sandbox-channels.ts, src/lib/sandbox-channels.test.ts
New module and tests introducing ChannelDef, KNOWN_CHANNELS (telegram, discord, slack), and helpers: getChannelDef, knownChannelNames, listChannels, getChannelTokenKeys, persistChannelTokens, clearChannelTokens.
CLI: nemoclaw channels
src/nemoclaw.ts
Adds sandbox-scoped channels action with list, add, remove. add/remove parse/validate channel, support --dry-run, reuse/get stored credentials, persist/clear ~/.nemoclaw/credentials.json, enforce non-interactive behavior, and prompt/queue sandbox rebuilds.
Onboarding
src/lib/onboard.ts
Replaced hardcoded messaging list with listChannels(), updated interactive prompt bounds, and exported isNonInteractive.
Sandbox startup guard & tests
scripts/nemoclaw-start.sh, test/nemoclaw-start.test.ts
Extended openclaw() guard to intercept openclaw channels subcommands: allow read/help (list, empty, -h, --help), reject mutators with actionable host-side nemoclaw <sandbox> channels ... instructions and return 1. Added Vitest asserting guard behavior.
Documentation & Troubleshooting
.agents/skills/nemoclaw-user-reference/references/commands.md, .agents/skills/nemoclaw-user-reference/references/troubleshooting.md, docs/reference/commands.md, docs/reference/troubleshooting.md
Adds CLI reference for `nemoclaw channels list

Sequence Diagram

sequenceDiagram
    participant User as "User"
    participant Host as "nemoclaw (host)"
    participant Creds as "~/.nemoclaw/credentials.json"
    participant Rebuild as "sandboxRebuild"
    participant Sandbox as "NemoClaw Sandbox"

    User->>Host: nemoclaw <name> channels add <channel>
    Host->>Host: validate channel (getChannelDef)
    Host->>Host: determine token keys (getChannelTokenKeys)
    alt Interactive
        Host->>User: prompt for missing tokens
        User->>Host: provide tokens
    else Non-Interactive
        Host->>Host: fail if tokens missing or queue change
    end
    Host->>Creds: persistChannelTokens()
    Creds-->>Host: saved
    alt Rebuild requested/confirmed
        Host->>Rebuild: sandboxRebuild(--yes)
        Rebuild->>Sandbox: rebuild image with updated creds
        Sandbox-->>Rebuild: complete
    else Queued for manual rebuild
        Host-->>User: instruct "nemoclaw <name> rebuild"
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped to code three channel ways,
host-side list, add, remove — hoorays!
Tokens tucked in a cozy file,
rebuild asked or left awhile.
A tiny hop, a helpful smile.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% 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 pull request title 'feat(channels): add host-side channels command + block sandbox-side mutation' is clear, concise, and accurately summarizes the main changes: introducing host-side channel management and preventing sandbox-side mutations.
Linked Issues check ✅ Passed The pull request comprehensively addresses all coding requirements from issue #2097: implements host-side channels list/add/remove commands, blocks sandbox-side mutations with actionable errors, supports non-interactive mode with queued rebuilds, and consolidates channel metadata.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue #2097 requirements: host-side command implementation, sandbox guard implementation, channel metadata consolidation, and supporting tests/documentation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/2097-channels-host-side

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

@laitingsheng laitingsheng changed the title feat(channels): add host-side channels command + block sandbox-side m… feat(channels): add host-side channels command + block sandbox-side mutation Apr 21, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/lib/onboard.ts (1)

4503-4503: Derive the toggle hint from MESSAGING_CHANNELS.length.

Now that the channel list is data-driven, the selector prompt in setupMessagingChannels() is the only place still assuming exactly three entries (Press 1-3 to toggle). The next channel addition will make that prompt wrong even though the selector logic already supports arbitrary lengths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/onboard.ts` at line 4503, The selector prompt in
setupMessagingChannels() hardcodes "Press 1-3 to toggle" but MESSAGING_CHANNELS
is now data-driven; update setupMessagingChannels() to compute the toggle hint
from MESSAGING_CHANNELS.length (e.g., build "Press 1-N to toggle" or list the
valid indices dynamically) and replace the hardcoded string so the prompt always
reflects the current length of the MESSAGING_CHANNELS array.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/reference/troubleshooting.md`:
- Around line 452-456: Update the section heading to reflect the new behavior:
replace the current heading "`openclaw channels add` or `remove` fails with
`EACCES` inside the sandbox" with a phrasing that indicates NemoClaw surfaces an
actionable error (e.g. "NemoClaw surfaces an actionable error when `openclaw
channels add` or `remove` is run inside the sandbox"); ensure the body still
explains that the sandbox keeps /sandbox/.openclaw/openclaw.json read-only
(Landlock + filesystem hardening) and that NemoClaw intercepts and presents its
own user-facing error instead of a raw EACCES trace.

In `@src/lib/sandbox-channels.ts`:
- Around line 77-79: Extend the set of persisted keys cleared when removing a
channel so we remove all channel-specific identifiers (not just bot tokens):
update getChannelTokenKeys (and the corresponding logic used by
clearChannelTokens) to return envKey, appTokenEnvKey plus identifiers such as
DISCORD_SERVER_ID, DISCORD_USER_ID, DISCORD_REQUIRE_MENTION,
TELEGRAM_ALLOWED_IDS (and any other channel-specific keys used elsewhere).
Ensure clearChannelTokens iterates this expanded list and deletes every returned
key from persisted credentials so no stale identifiers remain after channels
remove; apply the same change to the analogous code referenced around lines
87-90.

In `@src/nemoclaw.ts`:
- Around line 2651-2653: Update the help text for the "nemoclaw <name> channels
list" entries to accurately reflect behavior: change the description from "Show
configured messaging channels" to something like "List supported/available
messaging channels (does not check configured credentials)" in both occurrences
(the help string around the "nemoclaw <name> channels list" entry at the top and
the duplicate at lines ~2875–2877). Locate the help text strings for the
channels subcommands in src/nemoclaw.ts (the "nemoclaw <name> channels list"
literal) and revise both descriptions so the wording matches the actual behavior
of listing static known channels rather than reading configured channels.
- Around line 1669-1746: sandboxChannelsAdd and sandboxChannelsRemove accept any
flags/extra args and proceed to mutate credentials; validate inputs first by
ensuring args contain exactly one non-flag positional (channelArg) and that all
flags are from the allowed set (only "--dry-run") before doing any token
prompting or calling persistChannelTokens/clearChannelTokens. Modify
sandboxChannelsAdd and sandboxChannelsRemove to parse args up-front: collect
positional args and flag names, reject when positional count != 1 or any flag is
not "--dry-run" (print usage/valid channels and exit), then continue with
existing logic (prompting, persisting, clearing) so no mutations occur for bad
input.

---

Nitpick comments:
In `@src/lib/onboard.ts`:
- Line 4503: The selector prompt in setupMessagingChannels() hardcodes "Press
1-3 to toggle" but MESSAGING_CHANNELS is now data-driven; update
setupMessagingChannels() to compute the toggle hint from
MESSAGING_CHANNELS.length (e.g., build "Press 1-N to toggle" or list the valid
indices dynamically) and replace the hardcoded string so the prompt always
reflects the current length of the MESSAGING_CHANNELS array.
🪄 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: Pro Plus

Run ID: 514261e2-a3fc-491c-af43-5ca94c3e6f67

📥 Commits

Reviewing files that changed from the base of the PR and between db3453b and 662eed7.

📒 Files selected for processing (10)
  • .agents/skills/nemoclaw-user-reference/references/commands.md
  • .agents/skills/nemoclaw-user-reference/references/troubleshooting.md
  • docs/reference/commands.md
  • docs/reference/troubleshooting.md
  • scripts/nemoclaw-start.sh
  • src/lib/onboard.ts
  • src/lib/sandbox-channels.test.ts
  • src/lib/sandbox-channels.ts
  • src/nemoclaw.ts
  • test/nemoclaw-start.test.ts

Comment thread docs/reference/troubleshooting.md Outdated
Comment thread src/lib/sandbox-channels.ts
Comment thread src/nemoclaw.ts
Comment thread src/nemoclaw.ts Outdated
… list behaviour

- `channels list` is a static reference, not a credentials check; update the
  main help block and the `channels` dispatch-fallback usage line so neither
  claims to show "configured" channels.
- Retitle the troubleshooting entry to describe the sandbox-side guard
  (`openclaw channels add|remove` is blocked), since users no longer see a raw
  `EACCES` — the entrypoint's guard intercepts and prints a redirect to the
  host-side verbs first.

Addresses review feedback on #2139.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@src/nemoclaw.ts`:
- Around line 1684-1714: The dry-run branch is checked after
getChannelTokenKeys() and prompting logic, so channels add --dry-run still reads
credentials and prompts; move the dryRun check up to immediately after channel
validation and before calling getChannelTokenKeys or any credential
lookup/prompting: if (dryRun) should log the --dry-run message referencing
channelArg and sandboxName and return early, avoiding creation/use of tokenKeys,
acquired, getCredential, isNonInteractive checks, and askPrompt calls.
🪄 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: Pro Plus

Run ID: 06297fdb-6cd2-48e8-b360-28c89d56d47f

📥 Commits

Reviewing files that changed from the base of the PR and between 662eed7 and 53a752e.

📒 Files selected for processing (3)
  • .agents/skills/nemoclaw-user-reference/references/troubleshooting.md
  • docs/reference/troubleshooting.md
  • src/nemoclaw.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/reference/troubleshooting.md
  • .agents/skills/nemoclaw-user-reference/references/troubleshooting.md

Comment thread src/nemoclaw.ts Outdated
…, clarify docs

- Replace the hardcoded "Press 1-3 to toggle" label in
  `setupMessagingChannels` with `Press 1-N to toggle`, where N is derived
  from `MESSAGING_CHANNELS.length`, so adding a future channel does not
  silently invalidate the prompt.
- Move the `--dry-run` short-circuit in `channels add` to fire right after
  channel validation, before any credential lookup or interactive prompt —
  dry-run now never reads credentials or asks the user for tokens.
- Expand the "channels blocked inside the sandbox" troubleshooting entry
  with a concrete explanation of what "baked config" means: the selected
  channel list is passed into `docker build` via
  `NEMOCLAW_MESSAGING_CHANNELS_B64` and written into
  `/sandbox/.openclaw/openclaw.json` as a layer of the sandbox image,
  then mounted read-only at runtime.

Addresses review feedback on #2139.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@wscurran wscurran added the priority: high Important issue that should be resolved in the next release label Apr 21, 2026
Copy link
Copy Markdown
Contributor

@ericksoa ericksoa left a comment

Choose a reason for hiding this comment

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

Reviewed: well-structured host-side channels management feature. Clean module extraction, proper sandbox guard, tests and docs all in order. All CodeRabbit findings addressed. LGTM.

@ericksoa ericksoa merged commit 8bfd4e9 into main Apr 21, 2026
17 checks passed
@cv cv added the v0.0.22 Release target label Apr 21, 2026
@miyoungc miyoungc mentioned this pull request Apr 22, 2026
13 tasks
miyoungc added a commit that referenced this pull request Apr 22, 2026
## Summary

Bumps the published doc version to `0.0.22` and documents the
user-visible CLI behavior changes to `nemoclaw <name> connect` that
landed since v0.0.21. Drafted via the `nemoclaw-contributor-update-docs`
skill against commits in `v0.0.21..origin/main`, filtered through
`docs/.docs-skip`.

## Changes

- **`docs/project.json`** and **`docs/versions1.json`**: bump the
published version from `0.0.20` to `0.0.22`; insert a `0.0.21` entry
into the version list so the history stays contiguous.
- **`docs/reference/commands.md`** → `nemoclaw <name> connect`: document
two new behaviors.
- Readiness poll with `NEMOCLAW_CONNECT_TIMEOUT` (integer seconds;
default `120`) that replaces the silent hang when the sandbox is not yet
`Ready` — right after onboarding, while the 2.4 GB image is still
pulling (#466).
- Post-connect hint is now agent-aware, names the correct TUI command
for the sandbox's agent, and tells you to use `/exit` to leave the chat
before `exit` returns you to the host shell (#2080).

Feature PRs that shipped their own docs in the same commit are
intentionally not re-documented here:

- `channels list/add/remove` (#2139) — command reference and the
"`openclaw channels` blocked inside the sandbox" troubleshooting entry
landed with the feature.
- `nemoclaw gc` (#2176) — documented as part of the destroy/rebuild
image cleanup PR.

Skipped per `docs/.docs-skip`:

- `e6bad533 fix(shields): verify config lock and fail hard on re-lock
failure (#2066)` — matched `skip-features: src/lib/shields.ts`.

Other commits in the range (#2141 OpenShell version bump, #1819 plugin
banner live inference probe, #2085 / #2146 Slack Socket Mode fixes,
#2110 axios proxy fix, #1818 NIM curl timeouts, #1824 onboard gateway
bootstrap recovery, and assorted CI / test / install plumbing) are
internal behavior refinements with no doc-relevant surface change.

## Type of Change

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

## Verification

- [x] `npx prek run --all-files` passes for the modified files via the
pre-commit hook, including `Regenerate agent skills from docs` (source ↔
generated parity confirmed)
- [ ] `npm test` passes — skipped; the one pre-existing
`test/cli.test.ts > unknown command exits 1` failure on `origin/main` is
unrelated to these markdown/JSON-only changes
- [ ] Tests added or updated for new or changed behavior — n/a, doc-only
- [x] No secrets, API keys, or credentials committed
- [x] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only) — not run
locally
- [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)
— n/a, no new pages

## AI Disclosure

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

---
Signed-off-by: Miyoung Choi <miyoungc@nvidia.com>

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

* **New Features**
* `connect` now displays the sandbox phase while waiting for readiness
and honors a configurable timeout via NEMOCLAW_CONNECT_TIMEOUT (default
120s).
* TTY hints are agent-aware and instruct using `/exit` before returning
to the host shell.

* **Documentation**
  * Command docs updated to describe polling, timeout, and TTY guidance.
* Project/docs metadata updated for versions 0.0.21 and 0.0.22 (package
version bumped to 0.0.22).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: high Important issue that should be resolved in the next release v0.0.22 Release target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Brev][Agent] nemoclaw lacks a supported way to remove/stop messaging channels; openclaw channels remove fails in non-root sandboxes

4 participants