Skip to content

fix(flutter): stop deleteAllModels from reporting success when listing fails - #608

Merged
Siddhesh2377 merged 1 commit into
RunanywhereAI:mainfrom
ayaangazali:bugfix/flutter-delete-all-models-silent-failure
Aug 9, 2026
Merged

fix(flutter): stop deleteAllModels from reporting success when listing fails#608
Siddhesh2377 merged 1 commit into
RunanywhereAI:mainfrom
ayaangazali:bugfix/flutter-delete-all-models-silent-failure

Conversation

@ayaangazali

@ayaangazali ayaangazali commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

What

RunAnywhereDownloads.deleteAllModels() built its delete request from list(), which reports a failed storage lookup as an empty list. When the lookup failed, the request went out with no model ids, commons had nothing to delete, and the caller received StorageDeleteResult(success: true) having deleted nothing.

This changes only the destructive path to read through getStorageInfoResult(), which carries success / error_message (storage_types.proto), and to surface a failed lookup as StorageDeleteResult(success: false) with the underlying message.

Why

A silent no-op is the worst outcome for a destructive call: a caller that asks to free space is told it succeeded while every model is still on disk, and there is nothing in the return value to retry on. list() already logs the failure, but the log is not visible to the caller and the [] is indistinguishable from "no models are downloaded".

list() keeps its tolerant empty-list contract — display callers such as the example app's storage view (storage_view.dart) legitimately want a list they can render. Only the delete path now requires a successful lookup before it acts.

Scope

  • One file, +17/-2. No API signature changes, no new types, no behavior change when the lookup succeeds.
  • No unit test added, per CONTRIBUTING's E2E-through-the-example-apps convention.

Testing

From sdk/runanywhere-flutter/packages/runanywhere (Flutter 3.44.6):

  • flutter pub get — OK
  • flutter analyze --no-pubNo issues found!

Rebased onto current main (3becbae55) and re-gated after the rebase.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability when deleting all downloaded models by validating storage information first.
    • Deletion now reports a failure if storage details cannot be retrieved, rather than indicating success without removing models.

Copilot AI review requested due to automatic review settings July 31, 2026 17:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 14e66271-0307-4c75-8929-ff308a4790e0

📥 Commits

Reviewing files that changed from the base of the PR and between a9b542f and e9a2c6b.

📒 Files selected for processing (1)
  • sdk/runanywhere-flutter/packages/runanywhere/lib/public/capabilities/runanywhere_downloads.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • sdk/runanywhere-flutter/packages/runanywhere/lib/public/capabilities/runanywhere_downloads.dart

📝 Walkthrough

Walkthrough

deleteAllModels retrieves model IDs through getStorageInfoResult, returns lookup errors, and deletes the IDs only after a successful lookup.

Changes

Model deletion

Layer / File(s) Summary
Storage-aware model deletion
sdk/runanywhere-flutter/packages/runanywhere/lib/public/capabilities/runanywhere_downloads.dart
deleteAllModels uses getStorageInfoResult, propagates lookup errors, and deletes the returned model IDs on success.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the fix to prevent deleteAllModels from reporting success when model listing fails.
Description check ✅ Passed The description clearly explains the bug, implementation, scope, rationale, and validation results for the Flutter SDK change.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
sdk/runanywhere-flutter/packages/runanywhere/lib/public/capabilities/runanywhere_downloads.dart (1)

274-288: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for both lookup outcomes.

Test that a failed StorageInfoResult returns success: false, preserves errorMessage, and does not call deleteProto. Test that a successful result forwards every modelId to StorageDeleteRequest.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/public/capabilities/runanywhere_downloads.dart`
around lines 274 - 288, Add regression tests for the download deletion flow
around getStorageInfoResult and DartBridgeStorage.instance.deleteProto: verify
failed storage lookup returns success: false, preserves the provided
errorMessage, and never calls deleteProto; verify successful lookup forwards
every modelId from info.models into StorageDeleteRequest.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/public/capabilities/runanywhere_downloads.dart`:
- Around line 274-283: Update deleteAllModels around getStorageInfoResult so
exceptions from the storage lookup are caught and returned as
StorageDeleteResult(success: false), using the exception message for
errorMessage with an appropriate fallback. Keep the initialization check outside
this catch and preserve the existing handling for unsuccessful infoResult
responses.

---

Nitpick comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/public/capabilities/runanywhere_downloads.dart`:
- Around line 274-288: Add regression tests for the download deletion flow
around getStorageInfoResult and DartBridgeStorage.instance.deleteProto: verify
failed storage lookup returns success: false, preserves the provided
errorMessage, and never calls deleteProto; verify successful lookup forwards
every modelId from info.models into StorageDeleteRequest.
🪄 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 Plus

Run ID: 7336c06c-d684-4552-b978-ffc9bd3cdf7a

📥 Commits

Reviewing files that changed from the base of the PR and between 3becbae and 8fd2c48.

📒 Files selected for processing (1)
  • sdk/runanywhere-flutter/packages/runanywhere/lib/public/capabilities/runanywhere_downloads.dart

Comment on lines +274 to +283
final infoResult = await getStorageInfoResult(
StorageInfoRequest(includeModels: true),
);
if (!infoResult.success) {
return StorageDeleteResult(
success: false,
errorMessage: infoResult.errorMessage.isNotEmpty
? infoResult.errorMessage
: 'Failed to list downloaded models',
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Return a failed result when the lookup throws.

list() catches exceptions from getStorageInfoResult at Lines 341-353. If the native storage lookup throws, execution never reaches the !infoResult.success branch. deleteAllModels() then throws instead of returning StorageDeleteResult(success: false).

Catch the lookup exception and map its message to errorMessage. Keep the initialization check outside the catch.

Proposed fix
-    final infoResult = await getStorageInfoResult(
-      StorageInfoRequest(includeModels: true),
-    );
+    late final StorageInfoResult infoResult;
+    try {
+      infoResult = await getStorageInfoResult(
+        StorageInfoRequest(includeModels: true),
+      );
+    } catch (e) {
+      return StorageDeleteResult(
+        success: false,
+        errorMessage: e.toString(),
+      );
+    }
📝 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 infoResult = await getStorageInfoResult(
StorageInfoRequest(includeModels: true),
);
if (!infoResult.success) {
return StorageDeleteResult(
success: false,
errorMessage: infoResult.errorMessage.isNotEmpty
? infoResult.errorMessage
: 'Failed to list downloaded models',
);
late final StorageInfoResult infoResult;
try {
infoResult = await getStorageInfoResult(
StorageInfoRequest(includeModels: true),
);
} catch (e) {
return StorageDeleteResult(
success: false,
errorMessage: e.toString(),
);
}
if (!infoResult.success) {
return StorageDeleteResult(
success: false,
errorMessage: infoResult.errorMessage.isNotEmpty
? infoResult.errorMessage
: 'Failed to list downloaded models',
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/public/capabilities/runanywhere_downloads.dart`
around lines 274 - 283, Update deleteAllModels around getStorageInfoResult so
exceptions from the storage lookup are caught and returned as
StorageDeleteResult(success: false), using the exception message for
errorMessage with an appropriate fallback. Keep the initialization check outside
this catch and preserve the existing handling for unsuccessful infoResult
responses.

@ayaangazali

Copy link
Copy Markdown
Contributor Author

The kotlin-android failure is pre-existing breakage on main, not from this diff — this PR changes one Dart file (runanywhere_downloads.dart) and no Kotlin.

The job fails on a single ktlint violation in a file this PR does not touch:

sdk/runanywhere-kotlin/src/main/kotlin/com/runanywhere/sdk/foundation/connect/AndroidConnectTransport.kt:449:9
Declarations and declarations with comments should have an empty space between.
(standard:spacing-between-declarations-with-comments)

It was introduced by #604 (merged 2026-07-30) and is present on main at 3becbae5, whose own pr-build run is failing for the same reason. Any branch rebased onto current main inherits it.

I've opened a one-line fix for it separately so this and the other affected PRs can go green — I'll link it here shortly. Not re-pushing this branch to chase an unrelated failure.

@ayaangazali

Copy link
Copy Markdown
Contributor Author

The one-line fix for that ktlint violation is #609 — merging it should turn kotlin-android green here and on the other affected PRs.

@ayaangazali
ayaangazali force-pushed the bugfix/flutter-delete-all-models-silent-failure branch from 8fd2c48 to 1604ed2 Compare August 1, 2026 01:39
@ayaangazali

Copy link
Copy Markdown
Contributor Author

Both failures came from the base, and that base is now fixed: #609 merged and main's own pr-build is green at 932f6b6f.

This branch was still sitting on the pre-#609 main, so it kept inheriting the two lint violations. Rebased onto current main and force-pushed — no change to the diff itself (still the one Dart file, +17/-2).

Re-gated after the rebase: flutter analyze --no-pub on packages/runanywhereNo issues found!

@ayaangazali

Copy link
Copy Markdown
Contributor Author

The rebase did fix the lint failures — kotlin-android now gets all the way past ktlint into the test phase, and swift-spm is no longer failing. What's left is a flaky timing test, not this diff.

HandleStreamAdapterTest > cancel to native latency is bounded FAILED
    java.lang.AssertionError at HandleStreamAdapterTest.kt:310
98 tests completed, 1 failed

That test asserts a wall-clock budget for coroutine cancellation to propagate across 5 consumers on Dispatchers.Default:

val torn = withTimeoutOrNull(250) { waitFor { unregisterCount.get() == 1 } } ?: false
...
assertTrue("cancel-to-native unregister fired in ${elapsed}ms (budget 250ms)", elapsed < 250)

A 250 ms deadline on a shared CI runner is inherently timing-sensitive. Supporting evidence that it is intermittent rather than broken: kotlin-android is green on #600 and #590 and on main at 932f6b6f, and this PR changes exactly one Dart file (runanywhere_downloads.dart) with no Kotlin, so it cannot affect Kotlin coroutine teardown. A different timing test in the same job (VoiceAgentStreamAdapterTest > last detach tears down the C registration) flaked the same way on my #582 earlier.

Not pushing a code change for this. Re-triggering the workflow with an identical diff; if it flakes again, a maintainer re-run should clear it.

@ayaangazali

Copy link
Copy Markdown
Contributor Author

Gentle nudge on this one, since #611, #612 and #613 have since merged past it. This branch is rebased on current main, all checks are green, and the diff is still the single Dart file (+17/-2).

Recap of the bug in one line: deleteAllModels() built its request from list(), which reports a failed storage lookup as an empty list, so a failed lookup produced a delete request with no model ids and a success: true result that deleted nothing.

Happy to rebase again or adjust the approach if you would rather handle the failed-lookup case differently.

@ayaangazali
ayaangazali force-pushed the bugfix/flutter-delete-all-models-silent-failure branch from afffd51 to e9a2c6b Compare August 7, 2026 19:13
@ayaangazali

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (a9b542f). This was not a no-op rebase: #605 changed the shapes this fix depends on, so the previous revision no longer compiled.

StorageInfoResult and StorageDeleteResult both dropped success / error_message in favour of optional SDKError error. Analyzing the old revision against current main gives:

error • The getter 'success' isn't defined for the type 'StorageInfoResult' • undefined_getter
error • The named parameter 'success' isn't defined • undefined_named_parameter
error • The named parameter 'errorMessage' isn't defined • undefined_named_parameter

The fix now propagates the lookup's own error instead of synthesising one:

final infoResult = await getStorageInfoResult(
  StorageInfoRequest(includeModels: true),
);
if (infoResult.hasError()) {
  return StorageDeleteResult(error: infoResult.error);
}

The defect itself is unchanged and still reproduces at main: deleteAllModels() builds its request from list(), which reports a failed lookup as [], so the request carries no model ids, commons no-ops, and the caller gets a result with no error after a destructive call that deleted nothing.

flutter analyze --no-pub on packages/runanywhere is clean, and all 26 checks were green on the previous revision. One file, +13/-2. No rush from my side, just keeping it current and compiling.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

…g fails

deleteAllModels() built its delete request from list(), which reports a
failed lookup as an empty list. When the storage lookup failed, the
delete request went out with no model ids, commons had nothing to do,
and the caller got a StorageDeleteResult with success: true even though
no model was deleted - a silent no-op on a destructive operation.

Read the models through getStorageInfoResult() instead, which carries
success/error_message, and surface a failed lookup as
StorageDeleteResult(success: false) with the underlying error message.

list() keeps its tolerant empty-list contract for display callers (e.g.
the example app's storage view); only the destructive path now requires
a successful lookup before acting.

Signed-off-by: ayaangazali <ayaangazali.work@gmail.com>
@ayaangazali
ayaangazali force-pushed the bugfix/flutter-delete-all-models-silent-failure branch from e9a2c6b to 738ea7d Compare August 8, 2026 18:48
@Siddhesh2377

Copy link
Copy Markdown
Collaborator

Thanks @ayaangazali, solid catch!

@Siddhesh2377
Siddhesh2377 merged commit ad4658d into RunanywhereAI:main Aug 9, 2026
26 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.

3 participants