# TASK-023: Tolerate a literal JSON null list response Status: implemented; all required checks pass; physical-device retest confirmed Owner: Claude Reviewer: Codex ## Outcome A repository whose branch (or any other list-shaped) endpoint returns the literal JSON body `null` — Go's `encoding/json` serializes a `nil` slice this way instead of `[]` — is treated as an empty list, not a parse failure. Previously this surfaced as a generic "Gitea 서버에서 오류가 발생했습니다" (server error) on both the repository-list row's last-pushed-branch indicator and the repository's reference (branch/tag) screen, even though the HTTP response itself was a completely successful `200 OK`. ## Incident evidence Reproduced live on the human product owner's authenticated device via wireless `adb`, against real repositories `RobotAutomationDevelopment-Dept/CSOT-T8-iPTN-AMA` and `CSOT-T8-MACRO_SA`. Confirmed via the Gitea container's own request log (Container Manager → 로그) that the branches request returned `200 OK` in ~7ms — not a server-side failure. A temporary debug-only diagnostic (`Log.e`, removed before commit) then captured the actual response body for the failing call: `bodyLen=5 bodyPreview=null` — the literal 4-character JSON `null` (plus a trailing artifact in the length count), not an array. `GiteaRepositoryBrowserApi.parseList` deserialized that to Kotlin `null` via Gson (no exception), then unconditionally threw `ApiFailure.InvalidResponse()` on a null parse result — a general-purpose fallback meant for a body that failed to deserialize at all, not for a body that correctly deserializes to "no items." ## Acceptance criteria - [x] A JSON `null` response body from any `parseList`-backed endpoint (branches, tags, commits, etc.) maps to an empty list rather than a thrown failure. - [x] Existing malformed/genuinely-invalid JSON still throws `ApiFailure.InvalidResponse()` (only the specific null-body case changed). - [x] A unit test reproduces the literal `null` body case for `listBranches`. - [x] Verified live on the reporting device: `CSOT-T8-iPTN-AMA` (the repository used for diagnosis) now opens correctly instead of showing the generic server-error screen. `CSOT-T8-MACRO_SA` was not individually re-confirmed by the human product owner, but is expected to behave identically since the fix is in the shared `parseList` helper both repositories go through. - [x] Build, unit tests, lint, and secret scan pass. ## Writable paths - `app/src/main/java/com/ubisam/ubgitea/data/repositories/GiteaRepositoryBrowserApi.kt` - `app/src/test/java/com/ubisam/ubgitea/data/repositories/GiteaRepositoryBrowserApiTest.kt` - `tasks/023-tolerate-null-list-responses.md` ## Required checks ```text ./gradlew assembleDebug ./gradlew testDebugUnitTest ./gradlew lintDebug ``` ## Evidence - `GiteaRepositoryBrowserApi.kt`'s private `parseList` helper: `gson.fromJson>(body, type) ?: throw ApiFailure.InvalidResponse()` changed to `?: emptyList()`. This is the single shared helper used by `listBranches`, `listTags`, `listCommits`, and repository listing, so the fix applies uniformly rather than only to the one endpoint that happened to be reported — the same Go nil-slice-to-null serialization can equally affect any of them. - A genuinely malformed body (unparseable JSON) still throws `ApiFailure.InvalidResponse()` via the existing `catch (_: JsonParseException)` — only a body that Gson *successfully* parses to `null` changed behavior. - Diagnostic method: added temporary `BuildConfig.DEBUG`-gated `Log.e` calls at three points (`loadReferences`'s catch, `listBranches`'s per-item mapping, and the raw response body) to narrow the failure down via live logcat over a wireless-`adb` connection to the reporting device, then removed all three before committing — none of that scaffolding is part of this change. - `./gradlew assembleDebug testDebugUnitTest lintDebug --rerun-tasks`: BUILD SUCCESSFUL, all unit tests passing, 0 lint errors. - `git diff --check`: clean. Secret-pattern scan of the diff: no matches. - Live device retest: reinstalled the fixed build over wireless `adb` on the reporting device (paired via wireless debugging in this session) and the human product owner confirmed `CSOT-T8-iPTN-AMA` now opens correctly. ## Review findings None yet — no Codex review pending (Codex retired, 2026-09-29).