Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/e2e/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,17 @@ After making changes, verify them in the live app:
4. Capture evidence: `agent-flutter screenshot /tmp/evidence.png`
5. Generate video: `ffmpeg -framerate 1 -pattern_type glob -i '/tmp/e2e-*.png' -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:-1:-1" -c:v libx264 -pix_fmt yuv420p /tmp/report.mp4`

### Process-in-progress 404 race (#9241)

When WebSocket auto-finalize races `POST /v1/conversations`, the server returns HTTP 404 (`Conversation in progress not found`). That is benign — the app must return `null` and must **not** surface a crash overlay or report to Crashlytics.

1. With auth + backend connected, start a short phone-mic capture, then stop while the listen WebSocket is active.
2. `agent-flutter snapshot -i --json` — confirm no crash/error dialog after the processing chip dismisses.
3. Optional: `agent-flutter find type snackbar` should not match a fatal error after stop.
4. Evidence: `agent-flutter screenshot /tmp/process-in-progress-404.png`

Hermetic regression: `cd app && flutter test test/backend/process_in_progress_conversation_test.dart`.

## Decision Tree

| Problem | Solution |
Expand Down
32 changes: 25 additions & 7 deletions app/lib/backend/http/api/conversations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,35 @@ Future<CreateConversationResponse?> processInProgressConversation() async {
body: jsonEncode({}),
);
if (response == null) return null;
return interpretProcessInProgressConversationResponse(
response,
reportUnexpectedFailure: (body) {
// TODO: Server returns 304 doesn't recover
PlatformManager.instance.crashReporter.reportCrash(
Exception('Failed to create conversation'),
StackTrace.current,
userAttributes: {'response': body},
);
},
);
}

/// Maps POST /v1/conversations status codes. HTTP 404 is benign when WebSocket
/// auto-finalize races this call and the in-progress pointer is already gone.
@visibleForTesting
CreateConversationResponse? interpretProcessInProgressConversationResponse(
http.Response response, {
required void Function(String body) reportUnexpectedFailure,
}) {
Logger.debug('createConversationServer: ${response.body}');
if (response.statusCode == 200) {
return CreateConversationResponse.fromGeneratedWireJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
// TODO: Server returns 304 doesn't recover
PlatformManager.instance.crashReporter.reportCrash(
Exception('Failed to create conversation'),
StackTrace.current,
userAttributes: {'response': response.body},
);
}
if (response.statusCode == 404) {
Logger.debug('processInProgressConversation: in-progress conversation not found (likely already finalized)');
return null;
}
reportUnexpectedFailure(response.body);
return null;
}

Expand Down
56 changes: 56 additions & 0 deletions app/test/backend/process_in_progress_conversation_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:omi/backend/http/api/conversations.dart';

void main() {
group('interpretProcessInProgressConversationResponse', () {
test('HTTP 200 parses CreateConversationResponse', () {
const body = '''
{
"messages": [],
"conversation": {
"id": "conv-123",
"created_at": "2025-01-15T10:30:00.000Z",
"started_at": "2025-01-15T10:30:00.000Z",
"finished_at": null,
"summary": "Overview",
"structured": {"title": "Test", "overview": "Overview"}
}
}
''';

final result = interpretProcessInProgressConversationResponse(
http.Response(body, 200),
reportUnexpectedFailure: (_) => fail('unexpected failure callback'),
);

expect(result, isNotNull);
expect(result!.conversation?.id, 'conv-123');
expect(result.messages, isEmpty);
});

test('HTTP 404 returns null without reporting crash', () {
var reported = false;

final result = interpretProcessInProgressConversationResponse(
http.Response('{"detail":"Conversation in progress not found"}', 404),
reportUnexpectedFailure: (_) => reported = true,
);

expect(result, isNull);
expect(reported, isFalse);
});

test('HTTP 500 reports unexpected failure and returns null', () {
var reportedBody = '';

final result = interpretProcessInProgressConversationResponse(
http.Response('{"detail":"Internal Server Error"}', 500),
reportUnexpectedFailure: (body) => reportedBody = body,
);

expect(result, isNull);
expect(reportedBody, '{"detail":"Internal Server Error"}');
});
});
}
Loading