Skip to content

Stabilize NeuroRift↔OpenClaw integration: adapter hardening, sandboxing, and deterministic compose#27

Merged
Neuro-Rift merged 3 commits into
mainfrom
codex/integrate-neurorift-with-openclaw-gateway-pelm0q
Feb 27, 2026
Merged

Stabilize NeuroRift↔OpenClaw integration: adapter hardening, sandboxing, and deterministic compose#27
Neuro-Rift merged 3 commits into
mainfrom
codex/integrate-neurorift-with-openclaw-gateway-pelm0q

Conversation

@Neuro-Rift
Copy link
Copy Markdown
Owner

@Neuro-Rift Neuro-Rift commented Feb 27, 2026

User description

Motivation

  • Harden the NeuroRift <> OpenClaw gateway integration for production usage with stricter sandboxing, approval controls, heartbeat discipline, and deterministic runtime wiring.
  • Normalize runtime environment handling and fail-fast on malformed provider keys to avoid credential drift and silent misconfiguration.
  • Add operational artifacts (doctor, audit) and deterministic docker service topology so deployments are reproducible and observable.

Description

  • Implement a production-ready gateway adapter in integrations/openclaw/openclaw_gateway_adapter.py with 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 for rpc/event/lifecycle types.
  • Update runtime configuration in openclaw.json5 (bumped version to "1.1") to add a visible systemPrompt, operator/sandbox policies, approval forwarder rules, channel routing enforcement, heartbeat scheduling, observability/emission events, evolution controls, and runtime mode isolation constraints.
  • Replace and normalize compose topology across docker-compose.yml and docker-compose.dev.yml by renaming services (neurorift -> neurorift-core, openclaw -> rust-engine), adding gateway and sandbox-runner services, consolidating volumes and networks, tightening healthchecks, and wiring service envs to the new names/ports.
  • Add operational utilities and docs: scripts/openclaw_doctor.py for preflight checks and docs/OPENCLAW_INTEGRATION_AUDIT.md that records the hardening scope and compliance matrix; update BOOT.md to document mandatory preflight, deterministic docker runtime, and startup order.

Testing

  • Ran the preflight script with python3 scripts/openclaw_doctor.py which verified required env keys and returned success (exit 0) in a local check.
  • Validated compose configuration with docker compose -f docker-compose.yml config which succeeded and produced a consistent service graph for the updated topology.
  • Performed a local smoke orchestration by bringing up the core services (docker compose up -d --build gateway neurorift-core rust-engine web-ui ollama sandbox-runner) and verified service health via docker 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

  • Adapter now enforces sandbox and terminal-only exec policies, rejects denied/unknown tools, and auto-denies high-risk commands pending human approval; such denials return structured RPC error responses to the gateway.
  • Startup now validates and normalizes required environment variables and fails fast on missing or malformed provider keys; a new preflight script warns about missing envs and port conflicts.
  • Adapter emits structured, redacted JSON diagnostics (heartbeat, approval requests/results, exec finished, webhook events) and preserves isolated session identity and channel/mention/group routing in responses.
  • Docker compose files and boot docs updated to a deterministic service topology (gateway, neurorift-core, rust-engine, sandbox-runner, ollama, web-ui) and documented startup sequence and heartbeat expectations.

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:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

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:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

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

    • Added gateway and sandbox-runner services for unified request routing and sandboxed execution.
    • Introduced heartbeat discipline, lifecycle signaling, and approval-forwarding for high-risk actions.
    • Added a preflight doctor CLI to validate required environment and port availability.
  • Documentation

    • Updated boot/runbook with stabilization, sandboxing, approval controls, and end-to-end validation.
    • Added integration audit documenting compliance matrix and residual risks.
  • Chores

    • Reworked Docker composition and bumped config version to 1.1 with expanded sandbox and policy controls.

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Feb 27, 2026

CodeAnt AI is reviewing your PR.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

This commit fixes the style issues introduced in ad49969 according to the output
from Black.

Details: #27
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82b20b8 and a1d4a91.

📒 Files selected for processing (1)
  • integrations/openclaw/openclaw_gateway_adapter.py

📝 Walkthrough
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main objective of the PR: stabilizing the NeuroRift-OpenClaw integration through adapter hardening, sandboxing policies, and deterministic Docker composition—all core themes reflected in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/integrate-neurorift-with-openclaw-gateway-pelm0q

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

@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label Feb 27, 2026
@deepsource-io
Copy link
Copy Markdown
Contributor

deepsource-io Bot commented Feb 27, 2026

DeepSource Code Review

We reviewed changes in 564df95...a1d4a91 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

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 ↗

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Feb 27, 2026

Nitpicks 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • Exec Policy Bypass
    Exec policy checks only the first token of the command (base command). Wrappers like sudo, absolute paths (/bin/rm), shell -c invocations or chained commands may bypass allow/deny logic. Deny rules should inspect the full command and normalized executable name.

  • Approval Flow
    The approval path for high-risk commands always creates a pending/deny result after notifying channels and never inspects external overrides. There's no polling/confirmation hook or state check to flip an approval once a human responds; this will cause safe-but-time-consuming operations to be denied by default.

  • Logger Initialization Order
    StructuredLogger is instantiated in the adapter init before normalize_env() is applied in run(). As a result the normalized OPENCLAW_REDACT_LOGS value set by normalize_env() may not be picked up by the logger, leading to inconsistent redaction behavior between startup and runtime.

  • Lifecycle Cancellation
    The lifecycle background task is cancelled but not awaited on shutdown; cancelling without awaiting can leave incomplete tasks or unhandled exceptions. Also sending the final lifecycle update inside finally may fail if the WS is already closing — should guard send with connection state/error handling.

  • Webhook Error Handling
    Discord/Telegram notification calls post messages but do not validate responses or raise on non-2xx results. This can silently drop approvals or hide downstream failures (network errors, bad webhook URL, auth issues).

signal.signal(signal.SIGTERM, stop_handler)
signal.signal(signal.SIGINT, stop_handler)

async with websockets.connect(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.
Suggested change
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
Copy link
Copy Markdown

codeant-ai Bot commented Feb 27, 2026

CodeAnt AI finished reviewing your PR.

Copy link
Copy Markdown

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

🧹 Nitpick comments (4)
integrations/openclaw/openclaw_gateway_adapter.py (3)

274-281: _validate_exec_policy doesn't use self—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_policy to 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_neurorift invocation.

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 AsyncClient for 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

📥 Commits

Reviewing files that changed from the base of the PR and between 564df95 and 82b20b8.

📒 Files selected for processing (7)
  • BOOT.md
  • docker-compose.dev.yml
  • docker-compose.yml
  • docs/OPENCLAW_INTEGRATION_AUDIT.md
  • integrations/openclaw/openclaw_gateway_adapter.py
  • openclaw.json5
  • scripts/openclaw_doctor.py

Comment thread docker-compose.yml
- gateway_state:/data/openclaw
- neurorift_sessions:/data/neurorift/sessions
environment:
OPENCLAW_CONFIG_PATH: /workspace/NeuroRift/openclaw.json5
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's see the full docker-compose.yml to understand all volume mounts
cat docker-compose.yml

Repository: 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 2

Repository: 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.json5 to docker/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.

Comment thread docker-compose.yml
rust-engine:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "echo >/dev/tcp/localhost/18789"]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment thread docker-compose.yml
Comment on lines +128 to +129
NEURORIFT_BRIDGE_URL: "http://neurorift-core:8766"
OPENCLAW_WS_URL: "http://gateway:18789"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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.yml

Repository: demonking369/NeuroRift

Length of output: 1632


🏁 Script executed:

cat -n ./docker/web-ui/Dockerfile

Repository: 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 -20

Repository: demonking369/NeuroRift

Length of output: 1854


🏁 Script executed:

cat -n ./web-ui/next.config.js

Repository: 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.

Comment thread integrations/openclaw/openclaw_gateway_adapter.py
Comment on lines +335 to 349
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)},
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +400 to +404
def stop_handler(*_: Any) -> None:
self._stop_event.set()

signal.signal(signal.SIGTERM, stop_handler)
signal.signal(signal.SIGINT, stop_handler)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment thread openclaw.json5
},
],
relativeIntervals: [{ id: "heartbeat-cycle", every: "15m", mode: "isolated" }],
absoluteTimes: [{ id: "daily-summary", at: "2026-01-01T00:00:00Z", mode: "isolated" }],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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
@Neuro-Rift Neuro-Rift merged commit f2c87d8 into main Feb 27, 2026
2 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant