fix: Config corruption, gateway.mode & Node.js update#96
Conversation
, #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>
📝 WalkthroughWalkthroughPersist provider model entries as objects ( Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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.
| val gw = org.getJSONObject("gateway") | ||
| if (!gw.has("mode")) { | ||
| gw.put("mode", "local") | ||
| modified = true | ||
| } |
There was a problem hiding this comment.
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>
| 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 | |
| } |
Build FailedThe build failed for commit 071cf36. |
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
CHANGELOG.mdflutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.ktflutter_app/lib/constants.dartflutter_app/lib/models/ai_provider.dartflutter_app/lib/services/gateway_service.dartflutter_app/lib/services/provider_config_service.dartflutter_app/pubspec.yaml
💤 Files with no reviewable changes (1)
- flutter_app/lib/models/ai_provider.dart
| // Ensure gateway.mode=local so the gateway starts without --allow-unconfigured (#93, #90) | ||
| gw.putIfAbsent('mode', () => 'local'); |
There was a problem hiding this comment.
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.
| 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 | ||
| } |
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt
| 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) {} | ||
| } |
There was a problem hiding this comment.
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".
Build SuccessfulVersion: Download APKs
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>
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
CHANGELOG.mdflutter_app/lib/screens/splash_screen.dart
🚧 Files skipped from review as they are similar to previous changes (1)
- CHANGELOG.md
| // Regenerate bionic bypass if missing | ||
| if (!bypassOk) { | ||
| setState(() => _status = 'Repairing bionic bypass...'); | ||
| await NativeBridge.installBionicBypass(); | ||
| } |
There was a problem hiding this comment.
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.
| // 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.
| final nodeTarPath = '$filesDir/tmp/nodejs.tar.xz'; | ||
| final dio = Dio(); | ||
| await dio.download(nodeTarUrl, nodeTarPath); | ||
| await NativeBridge.extractNodeTarball(nodeTarPath); | ||
| } catch (_) {} |
There was a problem hiding this comment.
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.
| 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.
| // 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 (_) {} | ||
| } |
There was a problem hiding this comment.
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:
- Initial:
nodeOk=false,openclawOk=false - Node.js reinstalled successfully
- Condition:
!openclawOk && nodeOk→true && false→false - 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.
Summary
openclaw.jsonwere written as bare strings ("model-name") instead of objects ({ id: "model-name" }), causing OpenClaw config validation to reject the file. Fixed in both Node.js and Dart write paths. Existing corrupted configs are auto-repaired on startup.gateway.mode=localis set automatically in all config write paths and pre-seeded during initial setup._repairConfigFile()on everyGatewayService.init()to auto-fix corrupted configs before the gateway starts, preventing the 5-restart crash loop.mkdirs()fails silently on some devices.Test plan
openclaw.jsonhasmodels: [{ id: "..." }]notmodels: ["..."]/root/.openclaw/Summary by cubic
Fixes config corruption and gateway startup by auto-repairing
openclaw.jsonand defaultinggateway.modetolocal. Adds startup self-repair for missing Node.js/openclawafter upgrades, and updates Node.js to 22.14.0 with app 1.8.6+17.Bug Fixes
{ id }; auto-repair existingopenclaw.jsonon startup and when saving providers.gateway.mode=localacross all write paths and pre-seed it on first setup so the gateway starts cleanly.openclawin place when binaries are missing but rootfs exists (avoids re-setup after upgrades).org.jsonshadowing.Dependencies
openclaw-termuxto 1.8.6 and require Node >= 22.Written for commit 201713f. Summary will update on new commits.
Summary by CodeRabbit