All jobs

Fix Flutter Errors from Sentry Alerts

Yes. An agent reads the Sentry event from your Flutter app, reproduces it with a widget test, fixes the cause, runs analyze and flutter test, and opens a PR.

Run this on RushIn a Project that has your repository.

What the agent does

The agent reads the alert payload after the prompt, then opens the issue and its latest event through the Sentry connection. A Flutter event carries the Dart exception type and message, frames with the file under lib/ and the line, the device and OS tags, and the breadcrumbs the app recorded before the crash. It keeps the frame's file and line and the breadcrumb trail as the two facts the test must reproduce: which widget or provider threw, and what the user had done just before.

Then it reads the app. In an app shaped like simplezhli/flutter_deer, pubspec.yaml names flutter_deer 1.3.3+22 with sdk: ">=3.4.0 <4.0.0", provider ^6.1.2, fluro ^2.0.5, dio ^5.9.0, rxdart, sp_util and cached_network_image, and the workflow in .github/workflows/flutter-drive.yml pins Flutter 3.47.4. Errors already flow through one place: lib/util/handle_error_utils.dart wraps runApp in runZonedGuarded, redirects FlutterError.onError into the zone in production, and leaves a _reportError function whose production branch is a comment saying to upload the error, which is where a Sentry SDK call lives in a real app. The network layer is lib/net/dio_utils.dart, which builds a Dio with ResponseType.plain, accepts every status with validateStatus and leaves status handling to AdapterInterceptor in lib/net/intercept.dart, and lib/net/error_handle.dart, whose ExceptionHandle.handleException maps a DioException to codes 1000 to 1007 and anything else to 9999 through _errorMap. Screens are grouped by module under lib/ with a page, provider and sometimes presenter directory each; list screens extend BaseListProvider<T> from lib/order/provider/base_list_provider.dart, which calls notifyListeners() on every mutation, and presenters such as lib/order/presenter/order_search_presenter.dart fill the provider from requestNetwork callbacks, setting hasMore from data.items!.length == 30.

The agent matches the frame to one of those files and picks the test shape the repository already uses. A crash inside a widget gets a widget test in the style of test/login/login_accessibility_test.dart, which pumps MyApp(home: page, theme: ThemeProvider().getTheme()) so routes, theme and localisation are real, then drives the breadcrumb trail with tester.tap and tester.enterText on the Keys the pages already carry, such as phone, vcode and password in the register page. A crash in a presenter or in the error mapping gets a plain test case, with Dio given a fake HttpClientAdapter that returns the body or status the breadcrumbs show, because the one network test the repository has, test/net/dio_test.dart, calls api.github.com for real and cannot run offline. Either way the test fails with the same exception and message as the event before any fix is made.

The fix goes to the one widget, provider, presenter or mapping at fault: a null check before a bang on a nullable model field, a mounted check before a context use after an await, a _errorMap entry for a DioExceptionType the extension maps to a code with no entry, a guard around data.items!. Then flutter pub get regenerates DeerLocalizations as l10n.yaml directs, flutter analyze runs under analysis_options.yaml with strict-casts, strict-inference and strict-raw-types, and flutter test runs the suite, with the network test named as skipped. The pull request quotes the event, the failing test output and the passing analyze and test output.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "A Sentry alert fired for this Flutter app; its payload follows this prompt. Use the Sentry connection to read the issue and its latest event: the Dart exception type and message, the frames with their file and line under lib/, the device tags and the breadcrumbs before the crash. Read pubspec.yaml, analysis_options.yaml, lib/util/handle_error_utils.dart, lib/net/dio_utils.dart and lib/net/error_handle.dart, then the module directory the frames point at. Write a failing test first in the style the repository already has: a widget test that pumps MyApp with the page and ThemeProvider().getTheme() and drives the breadcrumb steps through the widgets' Keys, or a plain test for a presenter or the error mapping with Dio given a fake HttpClientAdapter. Confirm it fails with the event's exception, then fix the cause in that one widget, provider, presenter or mapping without catching the exception to hide it. Run flutter pub get, flutter analyze and flutter test, naming any test that needs the network as skipped. Open a pull request that links the Sentry issue, quotes the exception, frames and failing test output, and explains the cause. If it does not reproduce, stop and report what you tried.\n\nAlert payload:\n{payload}",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ],
  "connections": [
    "sentry"
  ]
}
  1. Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
  2. Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
  3. Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

What comes back

What comes back is a pull request that changes one file under lib/ and adds one test under test/, placed in the module directory the suite already uses, such as test/order/ or test/net/. Its description links the Sentry issue, quotes the exception and the frames, shows the test failing before the change and passing after, and gives the flutter analyze and flutter test output with the Flutter version used and the network test named as not run. pubspec.yaml does not change.

Before merging, a reviewer checks that the test drives the breadcrumb trail rather than calling the throwing method directly, so the fix is proven at the level the user hit it; that a null check does not silently blank a screen the user expected to be filled; and that the analyzer passed under the strict settings, which the quoted output shows. If the event came from a release older than the branch and the line has moved, the description says so and the test pins the current behaviour.

Run it on every Sentry issue alert

Create an alert rule in the Sentry project for the app with a webhook action pointed at a handler on your server. Sentry signs its own requests and cannot add the Authorization: Bearer header a Rush webhook trigger requires, so the handler receives the alert, places its JSON after the prompt and sends this request with the Sentry issue id as Idempotency-Key, which makes a redelivery return the run already started.

Set a webhook_url on the API key and Rush POSTs run.completed with the session_url when the turn ends, so the handler can read the pull request link and post it on the issue. A mobile crash arrives from many devices and Sentry folds them into one issue, so one issue is one run, and the handler can skip issues that already have an open pull request.

The use case this job follows, with its automation and trigger: Fix a bug from an alert.

Why run it on Rush?

A Flutter crash depends on a screen, a theme, a locale and a sequence of taps, and reproducing it by hand means a simulator and a guess. The sandbox pumps the real MyApp with the real theme in a widget test, drives the breadcrumb steps through the widget keys, and runs the strict analyzer and the suite with the pinned Flutter version, keeping every command's output on the session.

The pull request carries its own proof: the event the agent read, the test failing with that exception and passing after the change, and the analyze and test output a reviewer can check line by line. Several alerts become several sandboxes, and the reviewer judges a one-file diff instead of a stack trace.

Frequently asked questions

The repository has no Sentry SDK. Where does the event come from?

From your app, which reports with Sentry's Flutter SDK; the demo repository leaves a comment in _reportError where that call goes. The agent needs only the Sentry connection on the run to read the issue and its event, and the pull request does not add or configure the SDK.

Does it need a device or simulator?

No. Widget tests run in the Flutter test binding without a device, and that is where the reproduction happens. The driver suite under test_driver/ and the files under integration_test/ need a device, so the agent does not run them and says so.

How does it test code that calls the network?

By giving Dio a fake HttpClientAdapter that returns the body or status the breadcrumbs show, so DioUtils, the interceptors and ExceptionHandle run unchanged. The existing test/net/dio_test.dart calls GitHub's API for real, which the run names as skipped rather than counting as a failure.

Will it change the error handling in handle_error_utils.dart?

Only if the frames point there. The usual fix is in the widget or provider that threw. A change to the zone handler would alter what every future crash reports, so the agent leaves it alone unless the event shows it is the cause.

Which Flutter version does it use?

The one flutter-drive.yml pins, 3.47.4, since that is the version the project tests against; pubspec.yaml only requires 3.19.0 or later. The pull request names the version used, and the prompt can name another.