Stabilize NeuroRift↔OpenClaw integration: adapter hardening, sandboxing, and deterministic compose#27
Conversation
|
CodeAnt AI is reviewing your PR. |
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review infoConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
📝 Walkthrough🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
|
|
Overall Grade Focus Area: Reliability |
Security Reliability Complexity Hygiene |
Code Review Summary
| Analyzer | Status | Updated (UTC) | Details |
|---|---|---|---|
| Python | Feb 27, 2026 4:27p.m. | Review ↗ |
Nitpicks 🔍
|
| signal.signal(signal.SIGTERM, stop_handler) | ||
| signal.signal(signal.SIGINT, stop_handler) | ||
|
|
||
| async with websockets.connect( |
There was a problem hiding this comment.
Suggestion: The SIGINT/SIGTERM signal handler only sets a stop flag, but the main receive loop awaits ws.recv() directly, so the adapter can hang and ignore shutdown signals until a frame arrives or the connection drops, preventing timely and deterministic termination. [logic error]
Severity Level: Major ⚠️
- ❌ Adapter ignores SIGINT/SIGTERM during idle websocket periods.
- ⚠️ Docker stop may SIGKILL adapter after timeout.
- ⚠️ Graceful shutdown guarantees in `BOOT.md` are weakened.| async with websockets.connect( | |
| recv_task = asyncio.create_task(ws.recv()) | |
| stop_task = asyncio.create_task(self._stop_event.wait()) | |
| done, pending = await asyncio.wait( | |
| {recv_task, stop_task}, | |
| return_when=asyncio.FIRST_COMPLETED, | |
| ) | |
| for task in pending: | |
| task.cancel() | |
| if stop_task in done and self._stop_event.is_set(): | |
| # Stop was requested; exit loop without waiting for more frames. | |
| break | |
| # Otherwise, we have an incoming frame to process. | |
| incoming = recv_task.result() |
Steps of Reproduction ✅
1. Start the OpenClaw gateway and adapter exactly as documented in `BOOT.md:52-55` by
running `openclaw gateway --config ./openclaw.json5` and then `python3
integrations/openclaw/openclaw_gateway_adapter.py`, which invokes
`NeuroRiftOpenClawAdapter.run()` at
`integrations/openclaw/openclaw_gateway_adapter.py:377-433`.
2. Observe that `run()` establishes a websocket connection to the gateway via
`websockets.connect(...)` at `integrations/openclaw/openclaw_gateway_adapter.py:386` and
then enters the receive loop `while not self._stop_event.is_set(): incoming = await
ws.recv()` at `405-406`.
3. Leave the system idle so that the gateway sends no frames (no user traffic from
configured channels in `openclaw.json5:55-58`), meaning the adapter task is blocked on the
`await ws.recv()` at `406` and will not reach the loop condition again until a frame
arrives or the connection closes.
4. Send a shutdown signal to the adapter process (e.g., press Ctrl+C in the terminal or
run `kill -TERM <pid>` / let `docker stop` send SIGTERM) so that the custom signal
handlers at `380-384` invoke `stop_handler()` which sets `self._stop_event`; because the
running task is still awaiting `ws.recv()` (and the loop condition at `405` is not
re-evaluated), the adapter ignores the stop event and continues running until a websocket
frame or disconnect occurs, preventing timely and deterministic termination.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** integrations/openclaw/openclaw_gateway_adapter.py
**Line:** 406:406
**Comment:**
*Logic Error: The SIGINT/SIGTERM signal handler only sets a stop flag, but the main receive loop awaits `ws.recv()` directly, so the adapter can hang and ignore shutdown signals until a frame arrives or the connection drops, preventing timely and deterministic termination.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.|
CodeAnt AI finished reviewing your PR. |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (4)
integrations/openclaw/openclaw_gateway_adapter.py (3)
274-281:_validate_exec_policydoesn't useself—should be@staticmethod.The method accesses only module-level constants (
DEFAULT_TOOL_DENY,DEFAULT_TOOL_ALLOW) and doesn't use instance state.Make it a static method
+ `@staticmethod` - def _validate_exec_policy(self, command: str) -> None: + def _validate_exec_policy(command: str) -> None: base_cmd = command.strip().split()[0] if command.strip() else ""Then update the call site at line 315:
- self._validate_exec_policy(command_preview) + self._validate_exec_policy(command_preview)(No change needed at call site since Python resolves
self._validate_exec_policyto the static method correctly.)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@integrations/openclaw/openclaw_gateway_adapter.py` around lines 274 - 281, The method _validate_exec_policy currently does not use instance state and should be marked as a static method: add the `@staticmethod` decorator above _validate_exec_policy and keep its signature the same; the function should still reference the module-level DEFAULT_TOOL_DENY and DEFAULT_TOOL_ALLOW constants and raise the same exceptions, and no call-site changes are required since self._validate_exec_policy will continue to resolve to the static method.
283-287: httpx.AsyncClient created per_call_neuroriftinvocation.Each bridge call creates a new HTTP client, incurring connection setup overhead. For the request-heavy bridge path, consider a shared client instance.
Use shared client for bridge calls
class NeuroRiftOpenClawAdapter: def __init__(self) -> None: self.logger = StructuredLogger() # ... existing init ... + self._http_client: httpx.AsyncClient | None = None + async def _get_http_client(self) -> httpx.AsyncClient: + if self._http_client is None: + self._http_client = httpx.AsyncClient(timeout=self.request_timeout) + return self._http_client async def _call_neurorift(self, payload: Dict[str, Any]) -> Dict[str, Any]: - async with httpx.AsyncClient(timeout=self.request_timeout) as client: - response = await client.post(f"{self.bridge_url}/execute", json=payload) - response.raise_for_status() - return response.json() + client = await self._get_http_client() + response = await client.post(f"{self.bridge_url}/execute", json=payload) + response.raise_for_status() + return response.json()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@integrations/openclaw/openclaw_gateway_adapter.py` around lines 283 - 287, The _call_neurorift method creates a new httpx.AsyncClient for every call which causes repeated connection setup; change the class to create a single shared AsyncClient (configured with self.request_timeout and any needed transport settings) stored on the adapter instance (e.g., self._http_client) and have _call_neurorift reuse that client when posting to f"{self.bridge_url}/execute". Also add proper lifecycle management: initialize the client in the adapter constructor or an async init helper and close it in the adapter's shutdown/cleanup method to avoid resource leaks.
201-202: httpx.AsyncClient created per notification request.Creating a new
AsyncClientfor each Discord/Telegram notification adds connection overhead. For production with frequent approvals, consider using a shared client instance with connection pooling.Optional: Use shared client
class ExecutionApprovalForwarder: """Forwards high-risk command approvals to Discord and Telegram.""" def __init__(self, logger: StructuredLogger, timeout_seconds: int = 300) -> None: self.logger = logger self.timeout_seconds = timeout_seconds + self._client = httpx.AsyncClient(timeout=10) + async def close(self) -> None: + await self._client.aclose() async def _notify_discord(self, content: str) -> None: webhook = os.getenv("OPENCLAW_DISCORD_WEBHOOK_URL") if not webhook: return - async with httpx.AsyncClient(timeout=10) as client: - await client.post(webhook, json={"content": content}) + await self._client.post(webhook, json={"content": content})Also applies to: 210-211
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@integrations/openclaw/openclaw_gateway_adapter.py` around lines 201 - 202, The code currently creates a new httpx.AsyncClient for each notification (the async with httpx.AsyncClient(...) and await client.post(webhook, json={"content": content}) calls), which causes connection churn; refactor to use a shared AsyncClient instance (e.g., a module- or class-level variable like shared_httpx_client) that is created once and reused for post(webhook, json=...) calls, ensure you remove the per-request async with, update all places that currently create a client (including the other occurrences that mirror lines 210-211) to use the shared client, and add proper lifecycle handling to close the shared AsyncClient on application shutdown.docker-compose.yml (1)
111-121: Sandbox-runner healthcheck doesn't validate actual sandbox readiness.The healthcheck just echoes a static string. Consider verifying the sandbox environment is properly initialized (e.g., workspace exists, required tools available).
Optional: more meaningful healthcheck
healthcheck: - test: ["CMD", "sh", "-c", "echo sandbox-ready"] + test: ["CMD", "sh", "-c", "test -d /workspace && echo sandbox-ready"]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docker-compose.yml` around lines 111 - 121, The current sandbox-runner healthcheck only echoes a static string; replace the healthcheck test (the "test" key under the sandbox-runner service) with a real validation command that checks sandbox readiness — for example, a shell one-liner that verifies the workspace directory exists and is writable and that required executables are present (e.g., test -d /workspace && test -w /workspace && command -v <tool1> >/dev/null && command -v <tool2> >/dev/null); update the array form (["CMD","sh","-c", "..."]) accordingly and keep sensible interval/timeout/retries values so Docker will mark the container unhealthy if those checks fail.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docker-compose.yml`:
- Line 94: The container cannot find the file referenced by OPENCLAW_CONFIG_PATH
so the gateway will fail on startup; fix by either (A) adding a COPY of
openclaw.json5 into the image in docker/openclaw/Dockerfile (ensure the file
lands at /workspace/NeuroRift/openclaw.json5) or (B) mounting the repository
file into the gateway service via docker-compose by adding a bind volume mapping
from ./openclaw.json5 to /workspace/NeuroRift/openclaw.json5; update the
Dockerfile or the gateway service volumes accordingly so the path referenced by
OPENCLAW_CONFIG_PATH exists at container runtime.
- Line 105: The healthcheck command "test: [\"CMD-SHELL\", \"echo
>/dev/tcp/localhost/18789\"]" relies on bash's /dev/tcp and may fail in minimal
shells; replace it with a portable TCP check using a tool available in the image
(e.g., use netcat/nc or curl): update the healthcheck "test" entry to run "nc
-zv localhost 18789" or "curl --silent --fail http://localhost:18789" (or the
equivalent invocation supported by your image) so the check works reliably
across shells.
- Around line 128-129: The docker envs NEURORIFT_BRIDGE_URL and OPENCLAW_WS_URL
are being baked into the Next.js build via next.config.js rewrites() (executed
during npm run build), so make the proxy routing dynamic at runtime: remove or
revert the rewrites() entries that reference those build-time envs and instead
implement a runtime proxy that reads process.env (or platform env) when handling
requests — for example add a lightweight proxy API route or middleware (e.g.,
/api/proxy or Next.js middleware/edge function) that forwards requests to
process.env.NEURORIFT_BRIDGE_URL and process.env.OPENCLAW_WS_URL; update the
Dockerfile/compose to only set runtime envs and point the front-end to the
runtime proxy endpoints rather than embedding the URLs at build time.
In `@integrations/openclaw/openclaw_gateway_adapter.py`:
- Around line 335-349: The except block after calling self._call_neurorift only
handles PermissionError and ValueError; add handlers for httpx.RequestError and
httpx.HTTPStatusError to prevent unhandled network/upstream failures from
crashing the handler. Import httpx, catch httpx.RequestError to return an RPC
response with the same "type"/"id"/"session" structure and an "error" like
{"code":"network_error","message":str(exc)} and catch httpx.HTTPStatusError to
return {"code":"upstream_error","message":str(exc)} (or similar mapping),
ensuring you reuse correlation_id and session_ctx["sessionKey"] as in the
existing handlers so all fields remain consistent.
- Around line 400-404: The current signal handlers set self._stop_event but the
blocking await ws.recv() call (ws.recv()) prevents noticing that flag; replace
the plain await ws.recv() with an asyncio.wait_for wrapper (e.g., await
asyncio.wait_for(ws.recv(), timeout=1.0)) inside the receive loop, catch
asyncio.TimeoutError to loop back and check self._stop_event.is_set(), and only
process messages when recv returns; also ensure the stop_handler still sets
self._stop_event so the loop can exit promptly when the timeout loop observes
it.
- Around line 183-195: The evaluate method in ExecutionApprovalForwarder
currently emits notifications but immediately returns an
ApprovalResult(approved=False); instead register the pending approval and await
an external callback or timeout instead of returning right away. Concretely: in
ExecutionApprovalForwarder.evaluate, create/insert a pending entry (keyed by
session_id or correlation_id) into a shared pending-approvals store with an
asyncio.Event or Future, emit the notification via self.logger.emit as you do
now, then await that Event/Future with a timeout driven by default_on_timeout;
when the webhook/callback handler (e.g., an ApprovalCallbackHandler or similar)
receives a user response it should set the result into the pending store and set
the Event/Future so evaluate can return an ApprovalResult(approved=True/False,
reason=...) accordingly, and on timeout return the denial ApprovalResult and
emit the final "approval.result" event.
In `@openclaw.json5`:
- Line 202: The absoluteTimes entry for id "daily-summary" in absoluteTimes uses
a past timestamp ("2026-01-01T00:00:00Z"); update this to a future ISO timestamp
or replace the absoluteTimes entry with a recurring schedule (e.g., a relative
interval or cron-like schedule) so the scheduler can run it going forward—locate
the absoluteTimes array and the object with id "daily-summary" and either set at
to a future date/time or convert the object to a recurring form (e.g.,
interval/cron field) consistent with the surrounding config.
---
Nitpick comments:
In `@docker-compose.yml`:
- Around line 111-121: The current sandbox-runner healthcheck only echoes a
static string; replace the healthcheck test (the "test" key under the
sandbox-runner service) with a real validation command that checks sandbox
readiness — for example, a shell one-liner that verifies the workspace directory
exists and is writable and that required executables are present (e.g., test -d
/workspace && test -w /workspace && command -v <tool1> >/dev/null && command -v
<tool2> >/dev/null); update the array form (["CMD","sh","-c", "..."])
accordingly and keep sensible interval/timeout/retries values so Docker will
mark the container unhealthy if those checks fail.
In `@integrations/openclaw/openclaw_gateway_adapter.py`:
- Around line 274-281: The method _validate_exec_policy currently does not use
instance state and should be marked as a static method: add the `@staticmethod`
decorator above _validate_exec_policy and keep its signature the same; the
function should still reference the module-level DEFAULT_TOOL_DENY and
DEFAULT_TOOL_ALLOW constants and raise the same exceptions, and no call-site
changes are required since self._validate_exec_policy will continue to resolve
to the static method.
- Around line 283-287: The _call_neurorift method creates a new
httpx.AsyncClient for every call which causes repeated connection setup; change
the class to create a single shared AsyncClient (configured with
self.request_timeout and any needed transport settings) stored on the adapter
instance (e.g., self._http_client) and have _call_neurorift reuse that client
when posting to f"{self.bridge_url}/execute". Also add proper lifecycle
management: initialize the client in the adapter constructor or an async init
helper and close it in the adapter's shutdown/cleanup method to avoid resource
leaks.
- Around line 201-202: The code currently creates a new httpx.AsyncClient for
each notification (the async with httpx.AsyncClient(...) and await
client.post(webhook, json={"content": content}) calls), which causes connection
churn; refactor to use a shared AsyncClient instance (e.g., a module- or
class-level variable like shared_httpx_client) that is created once and reused
for post(webhook, json=...) calls, ensure you remove the per-request async with,
update all places that currently create a client (including the other
occurrences that mirror lines 210-211) to use the shared client, and add proper
lifecycle handling to close the shared AsyncClient on application shutdown.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
BOOT.mddocker-compose.dev.ymldocker-compose.ymldocs/OPENCLAW_INTEGRATION_AUDIT.mdintegrations/openclaw/openclaw_gateway_adapter.pyopenclaw.json5scripts/openclaw_doctor.py
| - gateway_state:/data/openclaw | ||
| - neurorift_sessions:/data/neurorift/sessions | ||
| environment: | ||
| OPENCLAW_CONFIG_PATH: /workspace/NeuroRift/openclaw.json5 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's see the full docker-compose.yml to understand all volume mounts
cat docker-compose.ymlRepository: demonking369/NeuroRift
Length of output: 4119
🏁 Script executed:
# Search for Dockerfile(s) and check for openclaw.json5 handling
fd -t f "Dockerfile" --exec cat {} +Repository: demonking369/NeuroRift
Length of output: 5270
🏁 Script executed:
# Check if openclaw.json5 exists in the repository
fd -t f "openclaw.json5"Repository: demonking369/NeuroRift
Length of output: 80
🏁 Script executed:
# Search for all references to OPENCLAW_CONFIG_PATH or openclaw.json5
rg -i "openclaw.json5|OPENCLAW_CONFIG_PATH" --type-list | head -20
rg "openclaw.json5|OPENCLAW_CONFIG_PATH" -B 2 -A 2Repository: demonking369/NeuroRift
Length of output: 4302
Config file at specified path is not available in the container.
OPENCLAW_CONFIG_PATH: /workspace/NeuroRift/openclaw.json5 points to a path that is neither baked into the Docker image nor mounted as a volume. The openclaw.json5 file exists in the repository root, but docker/openclaw/Dockerfile does not include a COPY instruction for it, and the gateway service only mounts gateway_state and neurorift_sessions volumes. Since OPENCLAW_CONFIG_PATH is listed as a required environment variable (failFastRequired in the config itself), the gateway service will fail to start.
Either:
- Add
COPY openclaw.json5 /workspace/NeuroRift/openclaw.json5todocker/openclaw/Dockerfile, or - Mount the config file as a volume:
- ./openclaw.json5:/workspace/NeuroRift/openclaw.json5
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docker-compose.yml` at line 94, The container cannot find the file referenced
by OPENCLAW_CONFIG_PATH so the gateway will fail on startup; fix by either (A)
adding a COPY of openclaw.json5 into the image in docker/openclaw/Dockerfile
(ensure the file lands at /workspace/NeuroRift/openclaw.json5) or (B) mounting
the repository file into the gateway service via docker-compose by adding a bind
volume mapping from ./openclaw.json5 to /workspace/NeuroRift/openclaw.json5;
update the Dockerfile or the gateway service volumes accordingly so the path
referenced by OPENCLAW_CONFIG_PATH exists at container runtime.
| rust-engine: | ||
| condition: service_healthy | ||
| healthcheck: | ||
| test: ["CMD-SHELL", "echo >/dev/tcp/localhost/18789"] |
There was a problem hiding this comment.
Healthcheck relies on bash-specific /dev/tcp pseudo-device.
The TCP connectivity check using echo >/dev/tcp/localhost/18789 requires bash. If the container image uses a minimal shell (e.g., busybox/ash or dash), this will fail silently.
Proposed fix using netcat or curl
healthcheck:
- test: ["CMD-SHELL", "echo >/dev/tcp/localhost/18789"]
+ test: ["CMD-SHELL", "nc -z localhost 18789 || curl -sf http://localhost:18789/health 2>/dev/null"]📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| test: ["CMD-SHELL", "echo >/dev/tcp/localhost/18789"] | |
| healthcheck: | |
| test: ["CMD-SHELL", "nc -z localhost 18789 || curl -sf http://localhost:18789/health 2>/dev/null"] |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docker-compose.yml` at line 105, The healthcheck command "test:
[\"CMD-SHELL\", \"echo >/dev/tcp/localhost/18789\"]" relies on bash's /dev/tcp
and may fail in minimal shells; replace it with a portable TCP check using a
tool available in the image (e.g., use netcat/nc or curl): update the
healthcheck "test" entry to run "nc -zv localhost 18789" or "curl --silent
--fail http://localhost:18789" (or the equivalent invocation supported by your
image) so the check works reliably across shells.
| NEURORIFT_BRIDGE_URL: "http://neurorift-core:8766" | ||
| OPENCLAW_WS_URL: "http://gateway:18789" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and examine the web-ui Dockerfile
find . -path "*docker/web-ui/Dockerfile*" -type f
echo "---"
# Search for the build args in the codebase
rg -n "NEURORIFT_BRIDGE_URL|OPENCLAW_WS_URL" --type-list | head -20
echo "---"
# Check the docker-compose.yml for context
rg -B 5 -A 2 "NEURORIFT_BRIDGE_URL|OPENCLAW_WS_URL" docker-compose.ymlRepository: demonking369/NeuroRift
Length of output: 1632
🏁 Script executed:
cat -n ./docker/web-ui/DockerfileRepository: demonking369/NeuroRift
Length of output: 1865
🏁 Script executed:
# Search for how these variables are used in the web-ui source code
find ./web-ui -type f \( -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" \) | head -30
echo "---"
# Search for usage of these env vars in web-ui
rg "NEURORIFT_BRIDGE_URL|OPENCLAW_WS_URL" ./web-ui --type-list | head -5
echo "---"
rg "NEURORIFT_BRIDGE_URL|OPENCLAW_WS_URL" ./web-ui 2>/dev/null | head -20Repository: demonking369/NeuroRift
Length of output: 1854
🏁 Script executed:
cat -n ./web-ui/next.config.jsRepository: demonking369/NeuroRift
Length of output: 1075
Build args are baked into the compiled Next.js application at build time.
NEURORIFT_BRIDGE_URL and OPENCLAW_WS_URL are used in next.config.js within the rewrites() function (lines 10–11), which executes during the npm run build step in the Dockerfile's builder stage. These values are compiled into the Next.js standalone output and cannot be overridden at runtime by environment variables. This prevents the web-ui from running outside Docker or in a different network context without rebuilding the image.
To make these configurable at runtime, the proxy routing should be moved from build-time configuration to a runtime mechanism that reads environment variables when requests are processed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docker-compose.yml` around lines 128 - 129, The docker envs
NEURORIFT_BRIDGE_URL and OPENCLAW_WS_URL are being baked into the Next.js build
via next.config.js rewrites() (executed during npm run build), so make the proxy
routing dynamic at runtime: remove or revert the rewrites() entries that
reference those build-time envs and instead implement a runtime proxy that reads
process.env (or platform env) when handling requests — for example add a
lightweight proxy API route or middleware (e.g., /api/proxy or Next.js
middleware/edge function) that forwards requests to
process.env.NEURORIFT_BRIDGE_URL and process.env.OPENCLAW_WS_URL; update the
Dockerfile/compose to only set runtime envs and point the front-end to the
runtime proxy endpoints rather than embedding the URLs at build time.
| bridged = await self._call_neurorift(tool_call) | ||
| except PermissionError as exc: | ||
| return { | ||
| "type": "rpc.reject", | ||
| "id": str(uuid.uuid4()), | ||
| "session": {"id": self.session_id, "mode": "isolated"}, | ||
| "error": { | ||
| "code": "approval_required", | ||
| "message": approval.reason, | ||
| }, | ||
| "type": "rpc.response", | ||
| "id": correlation_id, | ||
| "session": {"id": session_ctx["sessionKey"], "mode": "isolated"}, | ||
| "error": {"code": "policy_denied", "message": str(exc)}, | ||
| } | ||
| except ValueError as exc: | ||
| return { | ||
| "type": "rpc.response", | ||
| "id": correlation_id, | ||
| "session": {"id": session_ctx["sessionKey"], "mode": "isolated"}, | ||
| "error": {"code": "invalid_request", "message": str(exc)}, | ||
| } |
There was a problem hiding this comment.
Error handling catches PermissionError and ValueError but not httpx exceptions.
If _call_neurorift fails with network errors (e.g., httpx.RequestError, httpx.HTTPStatusError), they propagate uncaught, potentially crashing the message handler.
Add httpx exception handling
except ValueError as exc:
return {
"type": "rpc.response",
"id": correlation_id,
"session": {"id": session_ctx["sessionKey"], "mode": "isolated"},
"error": {"code": "invalid_request", "message": str(exc)},
}
+ except httpx.HTTPStatusError as exc:
+ return {
+ "type": "rpc.response",
+ "id": correlation_id,
+ "session": {"id": session_ctx["sessionKey"], "mode": "isolated"},
+ "error": {"code": "bridge_error", "message": f"Bridge returned {exc.response.status_code}"},
+ }
+ except httpx.RequestError as exc:
+ return {
+ "type": "rpc.response",
+ "id": correlation_id,
+ "session": {"id": session_ctx["sessionKey"], "mode": "isolated"},
+ "error": {"code": "bridge_unavailable", "message": str(exc)},
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@integrations/openclaw/openclaw_gateway_adapter.py` around lines 335 - 349,
The except block after calling self._call_neurorift only handles PermissionError
and ValueError; add handlers for httpx.RequestError and httpx.HTTPStatusError to
prevent unhandled network/upstream failures from crashing the handler. Import
httpx, catch httpx.RequestError to return an RPC response with the same
"type"/"id"/"session" structure and an "error" like
{"code":"network_error","message":str(exc)} and catch httpx.HTTPStatusError to
return {"code":"upstream_error","message":str(exc)} (or similar mapping),
ensuring you reuse correlation_id and session_ctx["sessionKey"] as in the
existing handlers so all fields remain consistent.
| def stop_handler(*_: Any) -> None: | ||
| self._stop_event.set() | ||
|
|
||
| signal.signal(signal.SIGTERM, stop_handler) | ||
| signal.signal(signal.SIGINT, stop_handler) |
There was a problem hiding this comment.
Signal handlers set via signal.signal() won't interrupt blocking ws.recv().
When SIGTERM/SIGINT is received, _stop_event is set, but ws.recv() at line 428 blocks indefinitely until a message arrives. The adapter won't shut down gracefully until the next WebSocket message is received.
Proposed fix: Use asyncio.wait_for with timeout
try:
while not self._stop_event.is_set():
- incoming = await ws.recv()
+ try:
+ incoming = await asyncio.wait_for(ws.recv(), timeout=5.0)
+ except asyncio.TimeoutError:
+ continue
event = json.loads(incoming)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@integrations/openclaw/openclaw_gateway_adapter.py` around lines 400 - 404,
The current signal handlers set self._stop_event but the blocking await
ws.recv() call (ws.recv()) prevents noticing that flag; replace the plain await
ws.recv() with an asyncio.wait_for wrapper (e.g., await
asyncio.wait_for(ws.recv(), timeout=1.0)) inside the receive loop, catch
asyncio.TimeoutError to loop back and check self._stop_event.is_set(), and only
process messages when recv returns; also ensure the stop_handler still sets
self._stop_event so the loop can exit promptly when the timeout loop observes
it.
| }, | ||
| ], | ||
| relativeIntervals: [{ id: "heartbeat-cycle", every: "15m", mode: "isolated" }], | ||
| absoluteTimes: [{ id: "daily-summary", at: "2026-01-01T00:00:00Z", mode: "isolated" }], |
There was a problem hiding this comment.
absoluteTimes entry has a past date.
The daily-summary absolute time is set to "2026-01-01T00:00:00Z", which is in the past (current date is February 2026). This job may have already triggered or might behave unexpectedly depending on the scheduler's handling of past times.
Update to a future date or convert to relative interval
- absoluteTimes: [{ id: "daily-summary", at: "2026-01-01T00:00:00Z", mode: "isolated" }],
+ absoluteTimes: [{ id: "daily-summary", at: "2026-03-01T00:00:00Z", mode: "isolated" }],Or consider using a recurring schedule:
- absoluteTimes: [{ id: "daily-summary", at: "2026-01-01T00:00:00Z", mode: "isolated" }],
+ // Move daily-summary to a cron job for recurring execution📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| absoluteTimes: [{ id: "daily-summary", at: "2026-01-01T00:00:00Z", mode: "isolated" }], | |
| absoluteTimes: [{ id: "daily-summary", at: "2026-03-01T00:00:00Z", mode: "isolated" }], |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@openclaw.json5` at line 202, The absoluteTimes entry for id "daily-summary"
in absoluteTimes uses a past timestamp ("2026-01-01T00:00:00Z"); update this to
a future ISO timestamp or replace the absoluteTimes entry with a recurring
schedule (e.g., a relative interval or cron-like schedule) so the scheduler can
run it going forward—locate the absoluteTimes array and the object with id
"daily-summary" and either set at to a future date/time or convert the object to
a recurring form (e.g., interval/cron field) consistent with the surrounding
config.
… codex/integrate-neurorift-with-openclaw-gateway-pelm0q
User description
Motivation
Description
integrations/openclaw/openclaw_gateway_adapter.pywith environment normalization, structured logging, high-risk approval forwarding, terminal-only exec policy, sandbox/tool allow/deny enforcement, session/channel isolation, heartbeat emission, lifecycle handling, and strict frame handling forrpc/event/lifecycletypes.openclaw.json5(bumpedversionto "1.1") to add a visiblesystemPrompt, operator/sandbox policies, approval forwarder rules, channel routing enforcement, heartbeat scheduling, observability/emission events, evolution controls, and runtime mode isolation constraints.docker-compose.ymlanddocker-compose.dev.ymlby renaming services (neurorift->neurorift-core,openclaw->rust-engine), addinggatewayandsandbox-runnerservices, consolidating volumes and networks, tightening healthchecks, and wiring service envs to the new names/ports.scripts/openclaw_doctor.pyfor preflight checks anddocs/OPENCLAW_INTEGRATION_AUDIT.mdthat records the hardening scope and compliance matrix; updateBOOT.mdto document mandatory preflight, deterministic docker runtime, and startup order.Testing
python3 scripts/openclaw_doctor.pywhich verified required env keys and returned success (exit 0) in a local check.docker compose -f docker-compose.yml configwhich succeeded and produced a consistent service graph for the updated topology.docker compose up -d --build gateway neurorift-core rust-engine web-ui ollama sandbox-runner) and verified service health viadocker compose ps, with healthchecks reporting expected readiness.Codex Task
CodeAnt-AI Description
Harden OpenClaw gateway adapter, add preflight doctor, and make Docker topology deterministic
What Changed
Impact
✅ Shorter startup troubleshooting✅ Fewer accidental or unauthorized tool executions✅ Clearer approval and audit events💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by CodeRabbit
New Features
Documentation
Chores