Skip to content

fix: Config corruption, gateway.mode & Node.js update#96

Merged
mithun50 merged 4 commits intomainfrom
fix/config-repair-gateway-mode-node-update
Mar 30, 2026
Merged

fix: Config corruption, gateway.mode & Node.js update#96
mithun50 merged 4 commits intomainfrom
fix/config-repair-gateway-mode-node-update

Conversation

@mithun50
Copy link
Copy Markdown
Owner

@mithun50 mithun50 commented Mar 27, 2026

Summary

Test plan

  • Fresh install: verify gateway starts without "gateway.mode=local" error
  • Configure an AI provider (e.g. OpenRouter) → verify openclaw.json has models: [{ id: "..." }] not models: ["..."]
  • Manually corrupt config (set model to string) → restart app → verify auto-repair fixes it
  • Verify Node.js 22.14.0 downloads and installs correctly
  • Verify bionic bypass files are created in /root/.openclaw/

Summary by cubic

Fixes config corruption and gateway startup by auto-repairing openclaw.json and defaulting gateway.mode to local. Adds startup self-repair for missing Node.js/openclaw after upgrades, and updates Node.js to 22.14.0 with app 1.8.6+17.

  • Bug Fixes

    • Store model entries as objects { id }; auto-repair existing openclaw.json on startup and when saving providers.
    • Set gateway.mode=local across all write paths and pre-seed it on first setup so the gateway starts cleanly.
    • Add splash-screen self-repair to reinstall Node.js and openclaw in place when binaries are missing but rootfs exists (avoids re-setup after upgrades).
    • Harden bionic-bypass install (retry + parents) and fix Kotlin org.json shadowing.
  • Dependencies

    • Update Node.js to 22.14.0.
    • Bump app to 1.8.6+17.
    • Sync openclaw-termux to 1.8.6 and require Node >= 22.

Written for commit 201713f. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Prevented crash-restart loops by ensuring gateway mode is set to "local" during saves, installs, and startup repair.
    • Made bypass installation more reliable with retries, parent-directory handling, and pre-seeded default config.
  • New Features
    • Automatic on-startup repair of corrupted config files, including normalizing provider model entries to structured objects.
    • Expanded auto-repair to reinstall Node.js and the app runtime when binaries are missing but rootfs is intact.
  • Chores
    • Bumped app to 1.8.6 and embedded Node.js to 22.14.0; raised Node engine requirement to >=22 and removed an outdated Anthropic model.

, #93, #94, #87)

- Fix model entries written as strings instead of objects in openclaw.json
- Auto-set gateway.mode=local in all config write paths
- Add config auto-repair on GatewayService.init()
- Make bionic bypass installation more resilient with retry logic
- Pre-seed openclaw.json with gateway.mode=local during setup
- Update Node.js from 22.13.1 to 22.14.0
- Bump version to 1.8.6+17

Co-Authored-By: Mithun Gowda B <mithungowda.b7411@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

Persist provider model entries as objects ({ id: "..." }), auto-repair on-disk openclaw.json (ensure gateway.mode = "local" and fix model entries), make bionic-bypass installation more robust (mkdir/retry and pre-seed/repair openclaw.json), bump app/package and embedded Node to 1.8.6 / Node 22.14.0.

Changes

Cohort / File(s) Summary
Release & metadata
CHANGELOG.md, flutter_app/lib/constants.dart, flutter_app/pubspec.yaml, package.json
Bump app/package to 1.8.6, embedded Node to 22.14.0 and engines.node to >=22.0.0; update changelog with config repair and startup behavior.
Provider defaults & serialization
flutter_app/lib/models/ai_provider.dart, flutter_app/lib/services/provider_config_service.dart
Removed an outdated Anthropic default model; provider saves now serialize models as objects ({ "id": "<model>" }) in both Node/proot and direct file-write fallback and ensure gateway.mode = "local".
Gateway startup & repair
flutter_app/lib/services/gateway_service.dart
Run _repairConfigFile() during GatewayService.init(); Node-side and fallback writes now enforce gateway.mode = "local" and convert string model entries to { id: ... } via _repairModelEntries().
Bootstrap / bionic bypass install
flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt
installBionicBypass() strengthens mkdirs() checks/retries, pre-seeds openclaw.json with { "gateway": { "mode": "local" } } if missing, and repairs existing configs (string→object model entries) before persisting.
Auto-repair / splash flow
flutter_app/lib/screens/splash_screen.dart
Expanded auto-repair to reinstall bionic bypass, Node.js (via Dio download + extract), and OpenClaw conditionally; recomputes setupComplete after attempts and wraps reinstall steps in silent try/catch.

Sequence Diagram(s)

sequenceDiagram
    participant App as Flutter App
    participant Bootstrap as BootstrapManager (Android)
    participant FS as File System
    participant Gateway as GatewayService
    participant Node as Embedded Node / proot

    App->>Bootstrap: installBionicBypass()
    Bootstrap->>FS: ensure bypassDir/root/.openclaw (mkdirs, retry)
    Bootstrap->>FS: read `openclaw.json`
    alt missing or corrupted
        Bootstrap->>FS: write `openclaw.json` { gateway: { mode: "local" } }
    else repaired
        Bootstrap->>FS: write repaired `openclaw.json` (models: [{ id: ... }])
    end

    App->>Gateway: init()
    Gateway->>FS: _repairConfigFile() (load, ensure gateway.mode="local", repair model entries)
    Gateway->>Node: write Node-side config script (ensures gateway.mode and model objects)
    Gateway->>FS: fallback write if Node path unavailable (ensures same)
    Gateway->>Node: start gateway
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through files to mend the seam,
Models now wear tidy { id } gleam,
Gateway set to local, steady and calm,
Bootstrap retries with a hopeful psalm,
Version 1.8.6 — a cheerful hop and beam.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: Config corruption, gateway.mode & Node.js update' directly summarizes the main changes: fixing config corruption issues, addressing gateway.mode defaults, and updating Node.js version.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/config-repair-gateway-mode-node-update

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt">

<violation number="1" location="flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt:1272">
P2: `gateway` repair path can abort on non-object values; use a safe object check so corrupted configs are still repaired.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +1272 to +1276
val gw = org.getJSONObject("gateway")
if (!gw.has("mode")) {
gw.put("mode", "local")
modified = true
}
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 27, 2026

Choose a reason for hiding this comment

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

P2: gateway repair path can abort on non-object values; use a safe object check so corrupted configs are still repaired.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt, line 1272:

<comment>`gateway` repair path can abort on non-object values; use a safe object check so corrupted configs are still repaired.</comment>

<file context>
@@ -1239,6 +1247,61 @@ require('/root/.openclaw/proot-compat.js');
+                    org.put("gateway", org.json.JSONObject().put("mode", "local"))
+                    modified = true
+                } else {
+                    val gw = org.getJSONObject("gateway")
+                    if (!gw.has("mode")) {
+                        gw.put("mode", "local")
</file context>
Suggested change
val gw = org.getJSONObject("gateway")
if (!gw.has("mode")) {
gw.put("mode", "local")
modified = true
}
val gw = org.optJSONObject("gateway")
if (gw == null) {
org.put("gateway", org.json.JSONObject().put("mode", "local"))
modified = true
} else if (!gw.has("mode")) {
gw.put("mode", "local")
modified = true
}
Fix with Cubic

@github-actions
Copy link
Copy Markdown

Build Failed

The build failed for commit 071cf36.

View build logs

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@flutter_app/lib/services/gateway_service.dart`:
- Around line 183-184: The code only uses gw.putIfAbsent('mode', () => 'local')
which leaves gateway.mode as null or empty string; update the repair/init logic
to treat null or empty values as corrupted by checking if gw['mode'] == null ||
(gw['mode'] is String && (gw['mode'] as String).isEmpty) and then set gw['mode']
= 'local'; apply the same fix to the other repair block that currently uses
putIfAbsent (the second gateway-mode patch) so both direct-write and import
paths enforce "local" when mode is missing, null, or empty.
- Around line 206-214: _repairConfigFile() returns early on empty or unparseable
JSON but then relies on an in-place rewrite that can be interrupted; change the
repair logic in _repairConfigFile() (and the similar block at lines ~247-250) to
perform the recovery atomically by writing the rebuilt JSON to a temporary file
(e.g., same directory, unique suffix) and then renaming/moving it over
openclaw.json in a single atomic replace operation; ensure the code proceeds to
create the temp-and-rename even when jsonDecode fails (do not return early), and
reuse the existing helper _writeNodeAllowConfig() or a new helper to perform the
safe temp-write + rename to avoid leaving a truncated file if the process is
killed mid-write.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 89a9fcaf-6191-4019-b178-f709fc621d49

📥 Commits

Reviewing files that changed from the base of the PR and between 1cbb6c1 and 2f32cf3.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt
  • flutter_app/lib/constants.dart
  • flutter_app/lib/models/ai_provider.dart
  • flutter_app/lib/services/gateway_service.dart
  • flutter_app/lib/services/provider_config_service.dart
  • flutter_app/pubspec.yaml
💤 Files with no reviewable changes (1)
  • flutter_app/lib/models/ai_provider.dart

Comment on lines +183 to +184
// Ensure gateway.mode=local so the gateway starts without --allow-unconfigured (#93, #90)
gw.putIfAbsent('mode', () => 'local');
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

Treat null/empty gateway.mode as corrupted too.

Both Dart repair paths only patch a missing key. {"gateway":{"mode":null}} or {"gateway":{"mode":""}} survives init/start on the direct-write path even though the gateway still needs "local", so imported or manually edited configs can still fail.

Suggested fix
-        gw.putIfAbsent('mode', () => 'local');
+        final mode = gw['mode'];
+        if (mode is! String || mode.isEmpty) {
+          gw['mode'] = 'local';
+        }
...
-      if (!gw.containsKey('mode')) {
-        gw['mode'] = 'local';
+      final mode = gw['mode'];
+      if (mode is! String || mode.isEmpty) {
+        gw['mode'] = 'local';
         modified = true;
       }

Also applies to: 219-223

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

In `@flutter_app/lib/services/gateway_service.dart` around lines 183 - 184, The
code only uses gw.putIfAbsent('mode', () => 'local') which leaves gateway.mode
as null or empty string; update the repair/init logic to treat null or empty
values as corrupted by checking if gw['mode'] == null || (gw['mode'] is String
&& (gw['mode'] as String).isEmpty) and then set gw['mode'] = 'local'; apply the
same fix to the other repair block that currently uses putIfAbsent (the second
gateway-mode patch) so both direct-write and import paths enforce "local" when
mode is missing, null, or empty.

Comment on lines +206 to +214
final content = configFile.readAsStringSync();
if (content.isEmpty) return;

Map<String, dynamic> config;
try {
config = Map<String, dynamic>.from(jsonDecode(content) as Map);
} catch (_) {
return; // Unparseable — _writeNodeAllowConfig will recreate it
}
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

Make the init-time repair recover from invalid JSON atomically.

_repairConfigFile() currently returns on empty/unparseable content, but this same method also rewrites openclaw.json in place. If the app is killed during that new startup write, the next init will keep bailing out on the truncated file and the config never self-heals unless some later write path happens to run.

Suggested fix
-      final content = configFile.readAsStringSync();
-      if (content.isEmpty) return;
-
-      Map<String, dynamic> config;
-      try {
-        config = Map<String, dynamic>.from(jsonDecode(content) as Map);
-      } catch (_) {
-        return; // Unparseable — _writeNodeAllowConfig will recreate it
-      }
-
-      bool modified = false;
+      final content = configFile.readAsStringSync();
+      bool modified = false;
+
+      Map<String, dynamic> config;
+      if (content.isEmpty) {
+        config = <String, dynamic>{};
+        modified = true;
+      } else {
+        try {
+          config = Map<String, dynamic>.from(jsonDecode(content) as Map);
+        } catch (_) {
+          config = <String, dynamic>{};
+          modified = true;
+        }
+      }
...
-      if (modified) {
-        configFile.writeAsStringSync(
-          const JsonEncoder.withIndent('  ').convert(config),
-        );
-      }
+      if (modified) {
+        final tmpFile = File('${configFile.path}.tmp');
+        tmpFile.writeAsStringSync(
+          const JsonEncoder.withIndent('  ').convert(config),
+        );
+        tmpFile.renameSync(configFile.path);
+      }

Also applies to: 247-250

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

In `@flutter_app/lib/services/gateway_service.dart` around lines 206 - 214,
_repairConfigFile() returns early on empty or unparseable JSON but then relies
on an in-place rewrite that can be interrupted; change the repair logic in
_repairConfigFile() (and the similar block at lines ~247-250) to perform the
recovery atomically by writing the rebuilt JSON to a temporary file (e.g., same
directory, unique suffix) and then renaming/moving it over openclaw.json in a
single atomic replace operation; ensure the code proceeds to create the
temp-and-rename even when jsonDecode fails (do not return early), and reuse the
existing helper _writeNodeAllowConfig() or a new helper to perform the safe
temp-write + rename to avoid leaving a truncated file if the process is killed
mid-write.

The local variable `org` shadowed the `org.json` package, causing
`Unresolved reference: json` at compile time. Renamed to `json`.

Co-Authored-By: Mithun Gowda B <mithungowda.b7411@gmail.com>
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt`:
- Around line 1264-1304: The catch block currently swallows parse errors so a
malformed openclaw.json never gets repaired; update the try/catch around
configFile.readText() and JSON parsing to catch the Exception as e, log the
error (e.g., using Android Log.e or your existing logger) and then attempt a
repair: back up the original configFile contents (e.g., write to configFile.name
+ ".bak"), create a new JSONObject or fallback object that ensures "gateway"
with "mode":"local" (and preserve/attempt to fix "models" entries if possible),
set modified = true and write the repaired JSON via
configFile.writeText(json.toString(2)); ensure any further IO exceptions are
logged rather than swallowed. Reference symbols: configFile, readText(),
writeText(), modified, "gateway", "models".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10bde38f-7e6b-48ba-a2bb-6befb05b46fe

📥 Commits

Reviewing files that changed from the base of the PR and between 2f32cf3 and 9cbfcd1.

📒 Files selected for processing (1)
  • flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt

Comment on lines +1264 to +1304
try {
val content = configFile.readText()
val json = org.json.JSONObject(content)
var modified = false
if (!json.has("gateway")) {
json.put("gateway", org.json.JSONObject().put("mode", "local"))
modified = true
} else {
val gw = json.getJSONObject("gateway")
if (!gw.has("mode")) {
gw.put("mode", "local")
modified = true
}
}
// Fix model entries: strings → objects with id field (#83, #88)
if (json.has("models")) {
val models = json.optJSONObject("models")
val providers = models?.optJSONObject("providers")
if (providers != null) {
val keys = providers.keys()
while (keys.hasNext()) {
val key = keys.next()
val prov = providers.optJSONObject(key)
val arr = prov?.optJSONArray("models")
if (arr != null) {
for (i in 0 until arr.length()) {
val item = arr.get(i)
if (item is String) {
arr.put(i, org.json.JSONObject().put("id", item))
modified = true
}
}
}
}
}
}
if (modified) {
configFile.writeText(json.toString(2))
}
} catch (_: Exception) {}
}
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

Malformed config can remain unrepaired due to swallowed parse errors.

At Line 1266, if JSON parsing fails, the empty catch at Line 1303 leaves openclaw.json untouched. That can preserve corruption and still miss gateway.mode, which conflicts with the repair-on-startup goal.

🔧 Proposed fix
-            try {
-                val content = configFile.readText()
-                val json = org.json.JSONObject(content)
+            try {
+                val content = configFile.readText().trim()
+                val json = try {
+                    if (content.isEmpty()) org.json.JSONObject() else org.json.JSONObject(content)
+                } catch (_: Exception) {
+                    org.json.JSONObject()
+                }
                 var modified = false
-                if (!json.has("gateway")) {
-                    json.put("gateway", org.json.JSONObject().put("mode", "local"))
-                    modified = true
-                } else {
-                    val gw = json.getJSONObject("gateway")
-                    if (!gw.has("mode")) {
-                        gw.put("mode", "local")
-                        modified = true
-                    }
+                val gw = json.optJSONObject("gateway") ?: org.json.JSONObject().also {
+                    json.put("gateway", it)
+                    modified = true
+                }
+                if (!gw.has("mode")) {
+                    gw.put("mode", "local")
+                    modified = true
                 }
                 // Fix model entries: strings → objects with id field (`#83`, `#88`)
                 if (json.has("models")) {
@@
                 if (modified) {
                     configFile.writeText(json.toString(2))
                 }
-            } catch (_: Exception) {}
+            } catch (_: Exception) {
+                // Last-resort recovery for unreadable/unparseable configs
+                configFile.writeText(
+                    org.json.JSONObject()
+                        .put("gateway", org.json.JSONObject().put("mode", "local"))
+                        .toString(2)
+                )
+            }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt`
around lines 1264 - 1304, The catch block currently swallows parse errors so a
malformed openclaw.json never gets repaired; update the try/catch around
configFile.readText() and JSON parsing to catch the Exception as e, log the
error (e.g., using Android Log.e or your existing logger) and then attempt a
repair: back up the original configFile contents (e.g., write to configFile.name
+ ".bak"), create a new JSONObject or fallback object that ensures "gateway"
with "mode":"local" (and preserve/attempt to fix "models" entries if possible),
set modified = true and write the repaired JSON via
configFile.writeText(json.toString(2)); ensure any further IO exceptions are
logged rather than swallowed. Reference symbols: configFile, readText(),
writeText(), modified, "gateway", "models".

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 27, 2026

Build Successful

Version: v1.8.6

Download APKs

Architecture Download
arm64-v8a (recommended) OpenClaw-v1.8.6-arm64-v8a.apk
armeabi-v7a (32-bit) OpenClaw-v1.8.6-armeabi-v7a.apk
x86_64 (emulator) OpenClaw-v1.8.6-x86_64.apk
Universal (all ABIs) OpenClaw-v1.8.6-universal.apk
App Bundle (Play Store) OpenClaw-v1.8.6.aab

Download from Actions Artifacts


Built from 663e1b1 by GitHub Actions

- Sync package.json version with Flutter app (1.8.6)
- Run npm update to refresh dependency tree
- Bump engine requirement from Node >= 18 to >= 22

Co-Authored-By: Mithun Gowda B <mithungowda.b7411@gmail.com>
…97)

Expanded splash screen auto-repair to handle missing node binary and
openclaw package when rootfs is intact. Previously only bionic-bypass
was auto-repaired; now node and openclaw are reinstalled in-place,
avoiding the "Setup OpenClaw" prompt after a node upgrade.

Co-Authored-By: Mithun Gowda B <mithungowda.b7411@gmail.com>
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="flutter_app/lib/screens/splash_screen.dart">

<violation number="1" location="flutter_app/lib/screens/splash_screen.dart:152">
P2: `nodeOk` is captured before the Node reinstall, so OpenClaw reinstall is skipped when Node was missing. Refresh `nodeOk` (or the bootstrap status) after the Node repair before checking `!openclawOk && nodeOk`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

}

// Reinstall openclaw if package.json is missing (#97)
if (!openclawOk && nodeOk) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 27, 2026

Choose a reason for hiding this comment

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

P2: nodeOk is captured before the Node reinstall, so OpenClaw reinstall is skipped when Node was missing. Refresh nodeOk (or the bootstrap status) after the Node repair before checking !openclawOk && nodeOk.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At flutter_app/lib/screens/splash_screen.dart, line 152:

<comment>`nodeOk` is captured before the Node reinstall, so OpenClaw reinstall is skipped when Node was missing. Refresh `nodeOk` (or the bootstrap status) after the Node repair before checking `!openclawOk && nodeOk`.</comment>

<file context>
@@ -125,9 +126,43 @@ class _SplashScreenState extends State<SplashScreen>
+            }
+
+            // Reinstall openclaw if package.json is missing (#97)
+            if (!openclawOk && nodeOk) {
+              setState(() => _status = 'Reinstalling OpenClaw...');
+              try {
</file context>
Fix with Cubic

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@flutter_app/lib/screens/splash_screen.dart`:
- Around line 131-135: The call to NativeBridge.installBionicBypass() is not
isolated in its own try/catch, so if it throws the outer catch prevents
subsequent repairs (Node/OpenClaw) from running; wrap the installBionicBypass()
invocation in a dedicated try/catch, setState before/after as currently done
using _status = 'Repairing bionic bypass...', and on error log or handle the
error locally so the method returns and allows the following reinstall blocks
(the existing Node/OpenClaw try/catch sections) to execute; reference
NativeBridge.installBionicBypass and the _status state update when making the
change.
- Around line 144-148: Create the missing parent directory before attempting the
download and make the download robust: ensure the directory for nodeTarPath is
created (use Directory('<filesDir>/tmp').create(recursive: true) or similar)
before calling dio.download, instantiate Dio with timeouts (use BaseOptions with
connect/receive/send timeouts) instead of plain Dio(), and stop silently
swallowing errors in the try/catch around
dio.download/NativeBridge.extractNodeTarball by catching the exception (catch
(e, st)) and logging or rethrowing so failures are visible; reference the
nodeTarPath variable, the Dio() instantiation, dio.download call,
NativeBridge.extractNodeTarball invocation, and the empty catch (_) {} to locate
the changes.
- Around line 151-164: The reinstall block for OpenClaw incorrectly uses the
original nodeOk value so OpenClaw is skipped when Node.js was just reinstalled;
fix by updating the condition after attempting Node.js installation—either
re-fetch the Node.js status using the same check that sets nodeOk (so nodeOk
reflects the current system) or set a local flag (e.g., nodeReinstalled = true)
when NativeBridge.runInProot succeeds and change the OpenClaw condition to if
(!openclawOk && (nodeOk || nodeReinstalled)); keep the existing calls to
NativeBridge.runInProot and NativeBridge.createBinWrappers for the OpenClaw
reinstall.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b6a95fa-3797-4f69-b864-6acacf3fd73a

📥 Commits

Reviewing files that changed from the base of the PR and between d92dd0b and 201713f.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • flutter_app/lib/screens/splash_screen.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md

Comment on lines +131 to +135
// Regenerate bionic bypass if missing
if (!bypassOk) {
setState(() => _status = 'Repairing bionic bypass...');
await NativeBridge.installBionicBypass();
}
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

Inconsistent error handling: bionic bypass reinstall lacks isolated try/catch.

Node.js and OpenClaw reinstalls each have their own try/catch blocks, allowing subsequent repairs to proceed if one fails. However, installBionicBypass() is not wrapped, so if it throws, the outer catch (line 168) triggers and skips Node.js/OpenClaw repairs entirely.

Proposed fix
            // Regenerate bionic bypass if missing
            if (!bypassOk) {
              setState(() => _status = 'Repairing bionic bypass...');
+             try {
                await NativeBridge.installBionicBypass();
+             } catch (_) {}
            }
📝 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
// Regenerate bionic bypass if missing
if (!bypassOk) {
setState(() => _status = 'Repairing bionic bypass...');
await NativeBridge.installBionicBypass();
}
// Regenerate bionic bypass if missing
if (!bypassOk) {
setState(() => _status = 'Repairing bionic bypass...');
try {
await NativeBridge.installBionicBypass();
} catch (_) {}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/screens/splash_screen.dart` around lines 131 - 135, The call
to NativeBridge.installBionicBypass() is not isolated in its own try/catch, so
if it throws the outer catch prevents subsequent repairs (Node/OpenClaw) from
running; wrap the installBionicBypass() invocation in a dedicated try/catch,
setState before/after as currently done using _status = 'Repairing bionic
bypass...', and on error log or handle the error locally so the method returns
and allows the following reinstall blocks (the existing Node/OpenClaw try/catch
sections) to execute; reference NativeBridge.installBionicBypass and the _status
state update when making the change.

Comment on lines +144 to +148
final nodeTarPath = '$filesDir/tmp/nodejs.tar.xz';
final dio = Dio();
await dio.download(nodeTarUrl, nodeTarPath);
await NativeBridge.extractNodeTarball(nodeTarPath);
} catch (_) {}
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

Missing parent directory creation before download.

The $filesDir/tmp/ directory may not exist, causing dio.download() to fail with a FileSystemException. The error is silently swallowed, leaving the user stuck with no indication of why the repair failed.

Additionally, Dio() is instantiated without a timeout, so the download could hang indefinitely on slow or stalled connections.

Proposed fix
              try {
                final arch = await NativeBridge.getArch();
                final nodeTarUrl = AppConstants.getNodeTarballUrl(arch);
                final filesDir = await NativeBridge.getFilesDir();
                final nodeTarPath = '$filesDir/tmp/nodejs.tar.xz';
-               final dio = Dio();
+               await Directory('$filesDir/tmp').create(recursive: true);
+               final dio = Dio(BaseOptions(
+                 connectTimeout: const Duration(seconds: 30),
+                 receiveTimeout: const Duration(minutes: 10),
+               ));
                await dio.download(nodeTarUrl, nodeTarPath);
                await NativeBridge.extractNodeTarball(nodeTarPath);
              } catch (_) {}
📝 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
final nodeTarPath = '$filesDir/tmp/nodejs.tar.xz';
final dio = Dio();
await dio.download(nodeTarUrl, nodeTarPath);
await NativeBridge.extractNodeTarball(nodeTarPath);
} catch (_) {}
final nodeTarPath = '$filesDir/tmp/nodejs.tar.xz';
await Directory('$filesDir/tmp').create(recursive: true);
final dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(minutes: 10),
));
await dio.download(nodeTarUrl, nodeTarPath);
await NativeBridge.extractNodeTarball(nodeTarPath);
} catch (_) {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/screens/splash_screen.dart` around lines 144 - 148, Create
the missing parent directory before attempting the download and make the
download robust: ensure the directory for nodeTarPath is created (use
Directory('<filesDir>/tmp').create(recursive: true) or similar) before calling
dio.download, instantiate Dio with timeouts (use BaseOptions with
connect/receive/send timeouts) instead of plain Dio(), and stop silently
swallowing errors in the try/catch around
dio.download/NativeBridge.extractNodeTarball by catching the exception (catch
(e, st)) and logging or rethrowing so failures are visible; reference the
nodeTarPath variable, the Dio() instantiation, dio.download call,
NativeBridge.extractNodeTarball invocation, and the empty catch (_) {} to locate
the changes.

Comment on lines +151 to +164
// Reinstall openclaw if package.json is missing (#97)
if (!openclawOk && nodeOk) {
setState(() => _status = 'Reinstalling OpenClaw...');
try {
const wrapper = '/root/.openclaw/node-wrapper.js';
const nodeRun = 'node $wrapper';
const npmCli = '/usr/local/lib/node_modules/npm/bin/npm-cli.js';
await NativeBridge.runInProot(
'$nodeRun $npmCli install -g openclaw',
timeout: 1800,
);
await NativeBridge.createBinWrappers('openclaw');
} catch (_) {}
}
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

Logic bug: OpenClaw reinstall is skipped when both Node.js and OpenClaw are missing.

The condition !openclawOk && nodeOk uses the original nodeOk value from the initial status check (line 125). If Node.js was missing and just reinstalled in lines 138-149, nodeOk remains false, so this block is always skipped in that scenario.

Trace:

  1. Initial: nodeOk=false, openclawOk=false
  2. Node.js reinstalled successfully
  3. Condition: !openclawOk && nodeOktrue && falsefalse
  4. OpenClaw reinstall skipped
Proposed fix: Re-fetch status after Node.js reinstall
            // Reinstall node if binary is missing (`#97`)
            if (!nodeOk) {
              setState(() => _status = 'Reinstalling Node.js...');
              try {
                final arch = await NativeBridge.getArch();
                final nodeTarUrl = AppConstants.getNodeTarballUrl(arch);
                final filesDir = await NativeBridge.getFilesDir();
                final nodeTarPath = '$filesDir/tmp/nodejs.tar.xz';
-               final dio = Dio();
+               await Directory('$filesDir/tmp').create(recursive: true);
+               final dio = Dio(BaseOptions(
+                 connectTimeout: const Duration(seconds: 30),
+                 receiveTimeout: const Duration(minutes: 10),
+               ));
                await dio.download(nodeTarUrl, nodeTarPath);
                await NativeBridge.extractNodeTarball(nodeTarPath);
+               // Update nodeOk after successful reinstall
+               final updatedStatus = await NativeBridge.getBootstrapStatus();
+               nodeOk = updatedStatus['nodeInstalled'] == true;
              } catch (_) {}
            }

            // Reinstall openclaw if package.json is missing (`#97`)
            if (!openclawOk && nodeOk) {

Alternatively, a simpler fix if re-fetching status is expensive:

Alternative: Track reinstall success with a local flag
            // Reinstall node if binary is missing (`#97`)
+           var nodeAvailable = nodeOk;
            if (!nodeOk) {
              setState(() => _status = 'Reinstalling Node.js...');
              try {
                // ... download and extract ...
                await NativeBridge.extractNodeTarball(nodeTarPath);
+               nodeAvailable = true;
              } catch (_) {}
            }

            // Reinstall openclaw if package.json is missing (`#97`)
-           if (!openclawOk && nodeOk) {
+           if (!openclawOk && nodeAvailable) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/screens/splash_screen.dart` around lines 151 - 164, The
reinstall block for OpenClaw incorrectly uses the original nodeOk value so
OpenClaw is skipped when Node.js was just reinstalled; fix by updating the
condition after attempting Node.js installation—either re-fetch the Node.js
status using the same check that sets nodeOk (so nodeOk reflects the current
system) or set a local flag (e.g., nodeReinstalled = true) when
NativeBridge.runInProot succeeds and change the OpenClaw condition to if
(!openclawOk && (nodeOk || nodeReinstalled)); keep the existing calls to
NativeBridge.runInProot and NativeBridge.createBinWrappers for the OpenClaw
reinstall.

@mithun50 mithun50 merged commit 4d382e5 into main Mar 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant