Skip to content
Merged
Show file tree
Hide file tree
Changes from 109 commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
4b7a4a2
refactor(cli): extract sandbox live state helpers
cv May 2, 2026
2ce87dd
refactor(cli): extract sandbox skill install action
cv May 2, 2026
4cd3cf3
refactor(cli): extract sandbox connect action
cv May 3, 2026
e9dd46e
refactor(cli): extract sandbox status action
cv May 3, 2026
aab6c86
refactor(cli): extract sandbox doctor action
cv May 3, 2026
8908521
refactor(cli): extract sandbox destroy action
cv May 3, 2026
56e4f05
refactor(cli): extract sandbox rebuild action
cv May 3, 2026
8bf1958
refactor(cli): extract upgrade sandboxes action
cv May 3, 2026
38eb84d
refactor(cli): remove runtime bridge
cv May 3, 2026
b2ad5da
refactor(cli): remove legacy dispatch fallbacks
cv May 3, 2026
edd2650
refactor(cli): expose explicit main entrypoint
cv May 3, 2026
a15da95
refactor(cli): add oclif examples for utility commands
cv May 3, 2026
4f57ebb
refactor(cli): validate logs flags with oclif
cv May 3, 2026
8b2d077
refactor(cli): improve sandbox diagnostic command metadata
cv May 3, 2026
a09cc51
refactor(cli): tighten policy and channel parser validation
cv May 3, 2026
75857dc
refactor(cli): improve snapshot command metadata
cv May 3, 2026
11c0676
refactor(cli): require skill install path in oclif
cv May 3, 2026
05f9eca
refactor(cli): add lifecycle confirmation flag aliases
cv May 3, 2026
4ebeae4
refactor(cli): split share into oclif subcommands
cv May 3, 2026
7d72437
Revert "refactor(cli): split share into oclif subcommands"
cv May 3, 2026
0ee5ca5
refactor(cli): split share into oclif subcommands
cv May 3, 2026
928219c
refactor(cli): model debug flags with oclif
cv May 3, 2026
36cce5e
refactor(cli): model onboard flags with oclif
cv May 3, 2026
702afb1
docs: sync oclif UX command reference
cv May 3, 2026
3224350
refactor(cli): extract public argv normalizer
cv May 3, 2026
cd4cdb8
refactor(cli): rename oclif dispatch module
cv May 3, 2026
9499ca6
refactor(cli): normalize policy command ids
cv May 3, 2026
42028ef
test(cli): require oclif command metadata
cv May 3, 2026
a05c6b3
refactor(cli): return typed debug parse results
cv May 3, 2026
1875fe9
refactor(cli): add public command display ids
cv May 3, 2026
36f1dbe
refactor(cli): use oclif summaries in root help
cv May 3, 2026
78bfd5a
refactor(cli): table-drive sandbox dispatch
cv May 3, 2026
96ce61f
refactor(cli): normalize gateway token command id
cv May 3, 2026
266ebc9
refactor(cli): render public oclif help
cv May 3, 2026
dc98994
refactor(cli): add shared oclif command base
cv May 3, 2026
d0d2a70
refactor(cli): use oclif flag relationships
cv May 3, 2026
8396c06
refactor(cli): pass lifecycle typed options
cv May 3, 2026
1532b19
refactor(cli): parse durations with oclif flags
cv May 3, 2026
7c0445a
refactor(cli): project public help through oclif
cv May 3, 2026
3978c32
test(cli): cover oclif metadata routing helpers
cv May 3, 2026
591eabb
test(cli): cover global oclif command adapters
cv May 3, 2026
8a6ecbc
test(cli): cover sandbox oclif command adapters
cv May 3, 2026
e5aa22f
refactor(cli): split share oclif commands
cv May 3, 2026
e179336
test(cli): narrow sandbox logs coverage ignores
cv May 3, 2026
a629813
test(cli): cover remaining global oclif adapters
cv May 3, 2026
c5954e7
test(cli): cover credentials oclif adapter
cv May 3, 2026
3380de9
test(cli): cover runtime utility helpers
cv May 3, 2026
f60a190
test(cli): improve global adapter coverage
cv May 3, 2026
78a16d1
merge: sync simple global adapter coverage
cv May 3, 2026
a58dc90
merge: sync credentials adapter coverage
cv May 3, 2026
034454f
merge: include credentials adapter coverage
cv May 3, 2026
28bc497
merge: sync combined global adapter coverage
cv May 3, 2026
e9ed6ba
test(cli): cover global action facade
cv May 3, 2026
00b288c
merge: sync global action facade coverage
cv May 3, 2026
7bd5f32
test(cli): stabilize coverage dist sourcemaps
cv May 4, 2026
d4b55d9
test(cli): rebaseline clean-dist function coverage
cv May 4, 2026
b872474
refactor(cli): extract maintenance image helpers
cv May 4, 2026
4965762
refactor(cli): extract upgrade sandbox helpers
cv May 4, 2026
9acd660
refactor(cli): extract sandbox destroy helpers
cv May 4, 2026
cc0eaa1
refactor(cli): extract sandbox logs helpers
cv May 4, 2026
847e115
refactor(cli): extract policy channel helpers
cv May 4, 2026
b6c0f5a
test(cli): allow clean coverage variance
cv May 4, 2026
5c98bd1
merge: sync coverage stabilization
cv May 4, 2026
4754f3e
merge: sync maintenance helper extraction
cv May 4, 2026
96f0758
merge: sync upgrade helper extraction
cv May 4, 2026
b6bd105
merge: sync destroy helper extraction
cv May 4, 2026
98fb75d
merge: sync logs helper extraction
cv May 4, 2026
5920d5b
test(cli): keep destroy helpers outside coverage ratchet
cv May 4, 2026
64d1646
merge: sync destroy helper coverage boundary
cv May 4, 2026
717c482
test(cli): keep logs helpers outside coverage ratchet
cv May 4, 2026
688074f
merge: sync logs helper coverage boundary
cv May 4, 2026
1314270
test(cli): keep policy channel helpers outside coverage ratchet
cv May 4, 2026
79d81c2
refactor(cli): group oclif core modules
cv May 4, 2026
5453a89
refactor(cli): split oclif commands by command tree
cv May 4, 2026
bbc297f
test(cli): reset credential command helper cache
cv May 4, 2026
25224c1
test(cli): avoid generic warning assertion in debug output
cv May 4, 2026
1ed9256
refactor(cli): group pure domain helpers
cv May 4, 2026
51799e4
refactor(cli): group openshell adapters
cv May 4, 2026
0b1e877
test(cli): enforce initial layer import boundaries
cv May 4, 2026
91d750b
refactor(cli): group sandbox actions
cv May 4, 2026
4321d63
refactor(cli): group global actions
cv May 4, 2026
5bc2242
test(cli): format layer boundary checker
cv May 4, 2026
073225e
ci(prek): tolerate unmatched biome format chunks
cv May 4, 2026
29a26ae
refactor(cli): group state modules
cv May 4, 2026
523e2d9
refactor(cli): group docker adapters
cv May 4, 2026
f25fcce
Merge branch 'test/layer-import-boundaries' into refactor/layer-sandb…
cv May 4, 2026
301b0a7
Merge branch 'refactor/layer-sandbox-actions' into refactor/layer-glo…
cv May 4, 2026
668235a
Merge branch 'refactor/layer-global-actions' into refactor/layer-stat…
cv May 4, 2026
392bebb
Merge branch 'refactor/layer-state-modules' into refactor/layer-docke…
cv May 4, 2026
e25e0a3
refactor(cli): discover oclif commands by pattern
cv May 6, 2026
bb7ebc3
refactor(cli): move coredns patcher behind internal command
cv May 6, 2026
489198d
refactor(cli): move dns proxy setup behind internal command
cv May 6, 2026
b5cdf50
refactor(uninstall): extract plan domain helpers
cv May 6, 2026
cc93aae
fix(uninstall): allow optional host env fields in plan tests
cv May 6, 2026
aa0f853
refactor(uninstall): run uninstall through internal command
cv May 6, 2026
cf416a4
fix(cli): locate node for uninstall wrapper
cv May 6, 2026
359fefd
test(cli): exclude source-tested uninstall helpers from coverage ratchet
cv May 6, 2026
f729544
refactor(cli): wrap debug script with TypeScript command
cv May 6, 2026
ab60b4c
refactor(cli): move dev shim install behind internal command
cv May 6, 2026
7f39bd5
ci(cli): typecheck cli sources during pre-push
cv May 6, 2026
9f8c6a3
refactor(cli): read metadata from oclif discovery
cv May 6, 2026
7e9d3c0
refactor(cli): colocate display metadata with commands
cv May 6, 2026
ca4388c
refactor(cli): derive legacy sandbox dispatch from metadata
cv May 6, 2026
70c78ec
refactor(cli): remove compatibility command index
cv May 6, 2026
9af5206
test(cli): cover oclif-native sandbox commands
cv May 6, 2026
28daa3e
refactor(installer): extract pure domain helpers
cv May 6, 2026
3489904
refactor(installer): add internal plan commands
cv May 6, 2026
46d30f5
refactor(runtime): extract entrypoint preload modules
cv May 6, 2026
bf278fa
merge(main): reconcile runtime preload extraction
cv May 6, 2026
292218f
fix(runtime): tolerate missing preload fixtures in chmod
cv May 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -224,17 +224,17 @@ RUN mkdir -p /sandbox/.nemoclaw/blueprints/0.1.0 \
# Copy startup script and shared sandbox initialisation library
COPY scripts/lib/sandbox-init.sh /usr/local/lib/nemoclaw/sandbox-init.sh
COPY scripts/nemoclaw-start.sh /usr/local/bin/nemoclaw-start
# Copy ws-proxy-fix.js to a Landlock-accessible path. OpenShell ≥0.0.36
# Copy NODE_OPTIONS preload modules to a Landlock-accessible path. OpenShell ≥0.0.36
# blocks /opt/nemoclaw-blueprint/ from non-root users, but the entrypoint
# needs to read this file to install the NODE_OPTIONS --require preload.
COPY nemoclaw-blueprint/scripts/ws-proxy-fix.js /usr/local/lib/nemoclaw/ws-proxy-fix.js
# needs to read these files to install runtime preloads under /tmp.
COPY nemoclaw-blueprint/scripts/*.js /usr/local/lib/nemoclaw/preloads/
COPY scripts/codex-acp-wrapper.sh /usr/local/bin/nemoclaw-codex-acp
COPY scripts/generate-openclaw-config.py /usr/local/lib/nemoclaw/generate-openclaw-config.py
COPY nemoclaw-blueprint/openclaw-plugins/ /usr/local/share/nemoclaw/openclaw-plugins/
RUN chmod 755 /usr/local/bin/nemoclaw-start /usr/local/bin/nemoclaw-codex-acp \
/usr/local/lib/nemoclaw/sandbox-init.sh \
/usr/local/lib/nemoclaw/generate-openclaw-config.py \
&& chmod 644 /usr/local/lib/nemoclaw/ws-proxy-fix.js \
&& chmod 644 /usr/local/lib/nemoclaw/preloads/*.js \
&& chmod 755 /usr/local/share/nemoclaw \
/usr/local/share/nemoclaw/openclaw-plugins \
&& find /usr/local/share/nemoclaw/openclaw-plugins -type d -exec chmod 755 {} + \
Expand Down
100 changes: 100 additions & 0 deletions nemoclaw-blueprint/scripts/ciao-network-guard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// ciao-network-guard.js — prevents @homebridge/ciao mDNS library from
// crashing the gateway when os.networkInterfaces() fails in restricted
// sandbox network namespaces.

(function () {
'use strict';

function basename(value) {
return String(value || '').split(/[\\/]/).pop();
}

function gatewayProcessFlavor() {
if (basename(process.argv0) === 'openclaw-gateway') return 'openclaw-gateway';
if (basename(process.title) === 'openclaw-gateway') return 'openclaw-gateway';
if (process.argv[2] === 'gateway') return 'launcher';
if (basename(process.argv[1]) === 'openclaw-gateway') return 'openclaw-gateway';
if (basename(process.argv[0]) === 'openclaw-gateway') return 'openclaw-gateway';
return '';
}

var _gatewayProcess = gatewayProcessFlavor();
if (_gatewayProcess) {
try {
process.stderr.write('[guard] ciao-network-guard loaded (' + _gatewayProcess + ')\n');
} catch (_) {}
}

// Monkey-patch os.networkInterfaces to return empty on failure.
var os = require('os');
var _origNetworkInterfaces = os.networkInterfaces;
// Rate-limit the failure log. The bonjour watchdog inside ciao retries
// advertising every few seconds, so a naive "log on every failure" fills
// sandbox logs with hundreds of identical lines per hour. Log the first
// failure (operator gets the actionable message) and at most one summary
// every 5 minutes thereafter, with a suppression count so volume is
// still observable. See GitHub issue #2611.
var _failureCount = 0;
var _lastLogMs = 0;
var _suppressedSinceLog = 0;
var _LOG_INTERVAL_MS = 5 * 60 * 1000;
os.networkInterfaces = function () {
try {
return _origNetworkInterfaces.call(os);
} catch (err) {
_failureCount++;
var nowMs = Date.now();
var shouldLog = _failureCount === 1 || (nowMs - _lastLogMs) >= _LOG_INTERVAL_MS;
if (shouldLog) {
var suffix = _suppressedSinceLog > 0
? ' [' + _suppressedSinceLog + ' suppressed in last ~5min, ' + _failureCount + ' total]'
: '';
process.stderr.write(
'[guard] os.networkInterfaces() failed: ' + (err.message || err) +
' — returning empty (mDNS disabled)' + suffix + '\n'
);
_lastLogMs = nowMs;
_suppressedSinceLog = 0;
} else {
_suppressedSinceLog++;
}
return {};
}
};

// Fallback: catch uncaughtException from ciao if the monkey-patch
// doesn't cover all call sites. Gateway-only — registering ANY
// uncaughtException listener tells Node "don't crash by default", and
// we want CLI processes (agent, doctor, plugins, tui) to keep default
// Node crash behavior so errors surface promptly.
//
// For gateway processes, non-ciao errors fall through (return) to the
// sandbox safety net registered later in the preload chain. The safety
// net is the single point of "keep gateway alive on unknown errors".
if (_gatewayProcess) {
process.on('uncaughtException', function (err, origin) {
if (
err && err.code === 'ERR_SYSTEM_ERROR' &&
String(err.message || '').indexOf('uv_interface_addresses') !== -1
) {
process.stderr.write(
'[guard] ciao/networkInterfaces crash caught: ' + (err.message || err) +
' \u2014 gateway continues\n'
);
return;
}
if (err && err.stack && err.stack.indexOf('ciao') !== -1 &&
String(err.message || '').indexOf('networkInterfaces') !== -1) {
process.stderr.write(
'[guard] ciao network error caught: ' + (err.message || err) +
' \u2014 gateway continues\n'
);
return;
}
// Not ciao — let the sandbox safety net handle it.
});
}
})();
9 changes: 3 additions & 6 deletions nemoclaw-blueprint/scripts/http-proxy-fix.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,10 @@
// OpenClaw's dist/ — there are no require() calls to intercept. The
// http.request wrapper sits below all libraries and catches every path.
//
// This file is the canonical source for review and tests. At sandbox boot
// This file is the canonical source for review and tests. The Dockerfile
// copies it into /usr/local/lib/nemoclaw/preloads/, then at sandbox boot
// nemoclaw-start.sh writes an identical copy to /tmp/nemoclaw-http-proxy-fix.js
// and loads it via NODE_OPTIONS=--require. A sync test enforces byte-for-byte
// equality. The content cannot be baked into /opt/nemoclaw-blueprint/scripts/
// because adding files to the optimized sandbox build context cache-busts the
// `COPY nemoclaw-blueprint/` Dockerfile layer and hangs npm ci in k3s
// Docker-in-Docker — see src/lib/sandbox-build-context.ts.
// and loads it via NODE_OPTIONS=--require.

(function () {
'use strict';
Expand Down
118 changes: 118 additions & 0 deletions nemoclaw-blueprint/scripts/nemotron-inference-fix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// nemotron-inference-fix.js — inject chat_template_kwargs for affected models.
//
// Problem (NemoClaw#1193, NemoClaw#2051):
// Nemotron models sometimes generate tool calls instead of text for simple
// queries, or return thinking-only blocks with stopReason "stop" that
// OpenClaw treats as end-of-turn, causing the conversation to stall.
// The root cause is the model's chat template producing empty assistant
// content when tool definitions are present.
//
// Fix:
// Inject `chat_template_kwargs: { force_nonempty_content: true }` into
// /v1/chat/completions request bodies when the model ID contains
// "nemotron". This tells the vLLM/NIM serving layer to force the chat
// template to always produce non-empty content alongside any tool calls
// or thinking blocks.
//
// Also inject `chat_template_kwargs: { thinking: false }` for
// deepseek-ai/deepseek-v4-pro, matching NVIDIA Build's tested invocation
// shape for the OpenAI-compatible chat-completions endpoint.
//
// Scoped strictly to known affected models — all other requests pass
// through untouched. Backends that do not support chat_template_kwargs
// silently ignore the extra field per the OpenAI-compatible API contract.

(function () {
'use strict';

var http = require('http');
var https = require('https');

var NEMOTRON_RE = /nemotron/i;
var DEEPSEEK_V4_PRO_RE = /^deepseek-ai\/deepseek-v4-pro$/i;
var COMPLETIONS_RE = /\/v1\/chat\/completions/;

function wrapModule(mod) {
var origRequest = mod.request;

mod.request = function (options, callback) {
// Only intercept object-form calls with a recognisable path.
if (typeof options === 'string' || !options) {
return origRequest.apply(mod, arguments);
}

var path = options.path || '';
if (options.method !== 'POST' || !COMPLETIONS_RE.test(path)) {
return origRequest.apply(mod, arguments);
}

// Create the real request, then intercept write/end to buffer the body.
var req = origRequest.apply(mod, arguments);
var origWrite = req.write;
var origEnd = req.end;
var chunks = [];
var intercepted = false;

req.write = function (chunk, encoding, cb) {
if (chunk != null) {
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk);
}
// Buffer instead of sending — we flush in end().
if (typeof encoding === 'function') { encoding(); }
else if (typeof cb === 'function') { cb(); }
return true;
};

req.end = function (chunk, encoding, cb) {
if (chunk != null && typeof chunk !== 'function') {
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk);
}
// Resolve the callback argument (end has multiple overload signatures).
var endCb = typeof chunk === 'function' ? chunk
: typeof encoding === 'function' ? encoding
: typeof cb === 'function' ? cb
: null;

var raw = Buffer.concat(chunks);
try {
var body = JSON.parse(raw.toString('utf-8'));
if (body && body.model && (NEMOTRON_RE.test(body.model) || DEEPSEEK_V4_PRO_RE.test(body.model))) {
if (!body.chat_template_kwargs) {
body.chat_template_kwargs = {};
}
if (NEMOTRON_RE.test(body.model)) {
body.chat_template_kwargs.force_nonempty_content = true;
}
if (DEEPSEEK_V4_PRO_RE.test(body.model)) {
body.chat_template_kwargs.thinking = false;
}
intercepted = true;
var modified = Buffer.from(JSON.stringify(body), 'utf-8');
// Update Content-Length so the proxy/server reads the full body.
if (req.getHeader && req.setHeader) {
req.removeHeader('content-length');
req.setHeader('Content-Length', modified.length);
}
origWrite.call(req, modified);
} else {
// Not a Nemotron model — send original bytes unmodified.
origWrite.call(req, raw);
}
} catch (_e) {
// JSON parse failed — forward original bytes.
origWrite.call(req, raw);
}

return endCb ? origEnd.call(req, endCb) : origEnd.call(req);
};

return req;
};
}

wrapModule(http);
wrapModule(https);
})();
131 changes: 131 additions & 0 deletions nemoclaw-blueprint/scripts/sandbox-safety-net.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// sandbox-safety-net.js — last-resort handler that keeps the gateway alive
// when any library throws an uncaught exception or unhandled rejection.
//
// Contract:
//
// 1. Inside the OpenShell sandbox the gateway is shared infrastructure.
// User-initiated actions (loading a plugin, starting a sidecar,
// running an agent against the gateway) must not be able to take it
// down. Node.js 22+ defaults --unhandled-rejections=throw which
// crashes on the first stray rejection from any library — including
// libraries we don't control.
//
// 2. Specific known-benign patterns are documented inline below. They
// get a single-line summary and are absorbed silently. Each pattern
// MUST document which library produces it, why it's safe to absorb
// in the sandbox context, and what the upstream fix is. Prefer
// disabling/configuring the upstream component so the rejection
// never fires; this list is the safety net, not the policy.
//
// 3. Unknown errors do NOT crash the gateway either, but they are
// logged with full stack so they can be diagnosed and either fixed
// upstream or added to the allow-list with explicit justification.
// "Unknown means crash" is the wrong default for shared
// infrastructure; "unknown means log loudly" is the right default.
//
// 4. No process.exit interception. An earlier iteration intercepted
// process.exit during swallow windows, which masked legitimate
// shutdown signals and was itself the kind of catch-all hack we
// want to avoid.
//
// 5. Only active when OPENSHELL_SANDBOX=1 (set by OpenShell at runtime),
// and only for gateway processes. The gateway can appear as the
// launcher (`openclaw gateway run ...`) or the re-execed
// `openclaw-gateway` child. CLI commands (agent, doctor, plugins,
// tui, etc.) get default Node behavior so errors surface promptly
// to users running short-lived tools.

(function () {
'use strict';
if (process.env.OPENSHELL_SANDBOX !== '1') return;

function basename(value) {
return String(value || '').split(/[\\/]/).pop();
}

function gatewayProcessFlavor() {
if (basename(process.argv0) === 'openclaw-gateway') return 'openclaw-gateway';
if (basename(process.title) === 'openclaw-gateway') return 'openclaw-gateway';
if (process.argv[2] === 'gateway') return 'launcher';
if (basename(process.argv[1]) === 'openclaw-gateway') return 'openclaw-gateway';
if (basename(process.argv[0]) === 'openclaw-gateway') return 'openclaw-gateway';
return '';
}

var _gatewayProcess = gatewayProcessFlavor();
if (!_gatewayProcess) return;

try {
process.stderr.write('[sandbox-safety-net] loaded (' + _gatewayProcess + ')\n');
} catch (_) {}

// KNOWN-BENIGN ERROR PATTERNS
//
// ciao / @homebridge/ciao — mDNS service-discovery library used by the
// OpenClaw bonjour plugin (introduced in 2026.4.15). Sandboxes have
// restricted network namespaces with no multicast. Two failure modes:
// - sync: os.networkInterfaces() throws ERR_SYSTEM_ERROR
// uv_interface_addresses. Pre-empted by ciao-network-guard.js,
// which monkey-patches os.networkInterfaces() to return {}.
// - async: the probe state machine cancels itself during gateway
// startup/reload and emits "CIAO PROBING CANCELLED" as an unhandled
// rejection. This is the path we catch here.
// Upstream fix: bonjour is disabled via plugins.entries.bonjour.enabled
// = false in the sandbox openclaw.json. This pattern is a backstop in
// case the disable is bypassed or a future release introduces another
// mDNS code path.
function classifyBenignRejection(reason) {
if (!reason) return null;
var msg = String((reason && reason.message) || reason);
var stack = (reason && reason.stack) || '';

if (msg.indexOf('CIAO') !== -1 ||
stack.indexOf('@homebridge/ciao') !== -1 ||
stack.indexOf('/ciao/') !== -1) {
return 'ciao/mDNS (sandbox lacks multicast; bonjour should be disabled in openclaw.json)';
}
if (reason && reason.code === 'ERR_SYSTEM_ERROR' &&
msg.indexOf('uv_interface_addresses') !== -1) {
return 'uv_interface_addresses (restricted netns)';
}
return null;
}

process.on('uncaughtException', function (err, origin) {
// Sync error paths are pre-empted by the targeted guards
// (ciao-network-guard.js, slack-channel-guard.js when Slack is
// configured). If we get here it's an error those guards didn't
// recognize. Log full stack and stay alive — registering this
// listener is what tells Node "don't crash on uncaughtException".
try {
process.stderr.write(
'[sandbox-safety-net] uncaughtException [unhandled by upstream guards \u2014 please diagnose]: ' +
((err && err.stack) ? err.stack : String(err)) +
' (origin: ' + origin + ') \u2014 gateway continues\n'
);
} catch (_) {}
});

process.on('unhandledRejection', function (reason, promise) {
var benign = classifyBenignRejection(reason);
if (benign) {
try {
process.stderr.write(
'[sandbox-safety-net] unhandledRejection [known-benign: ' + benign + ']: ' +
((reason && reason.message) ? reason.message : String(reason)) + '\n'
);
} catch (_) {}
return;
}
try {
process.stderr.write(
'[sandbox-safety-net] unhandledRejection [UNKNOWN PATTERN \u2014 please diagnose]: ' +
((reason && reason.stack) ? reason.stack : String(reason)) +
' \u2014 gateway continues\n'
);
} catch (_) {}
});
})();
Loading
Loading