Skip to content

fix(mobile): localize security agent sync errors - #7888

Merged
iscekic merged 6 commits into
mainfrom
drain/localize-security-sync-errors
Oct 21, 2026
Merged

iscekic merged 6 commits into
mainfrom
drain/localize-security-sync-errors

Conversation

@iscekic

@iscekic iscekic commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Change

The security operation-ledger markers the server raises for sync and dismissal failures were rendered verbatim: under ja the dashboard banner showed the raw English sentence above an otherwise translated screen.

Mapping now happens once at the mutation boundary, the same way pr-operation-ledger.ts handles the PR-review surface:

  • New apps/mobile/src/lib/security-agent-ledger.ts owns the raw marker constants, the retryability policy, and the two surface mappers. use-security-agent-mutations.ts and use-security-findings.ts import from it.
  • Known markers map to their catalog sentence (securityAgent.mutations.ambiguous, .settleFailed, .replayFailed, common.actionNotRecorded), the in-progress duplicate keeps the per-surface copy, and any other message reads one localized generic sentence (securityAgent.mutations.actionFailed) — the server's English reasons cannot be translated on the device.
  • The mapped error keeps the raw server error as cause and carries the retryability flag computed from the raw error, so the key-rotation and disabled-control contracts are unchanged after the message is replaced.
  • The shared persistence-failure sentence moves from prReview.operation.persistenceFailed to common.actionNotRecorded; the four PR-review call sites point at it.
  • dashboard-screen.tsx and dismiss-finding-screen.tsx drop the now-redundant missing-configuration special case: the mapped message is already localized.
  • The four new keys carry translations in all 87 catalogs; check:i18n and check:plurals pass.

Verification

  • pnpm --dir apps/mobile typecheck, lint, format:check, check:unused: clean; check:i18n / check:plurals: 87 catalogs match; full mobile suite: 1190 files, 17871 tests passed.
  • New unit coverage: every marker maps to its catalog copy and keeps its retryability; unrecognised messages map to the generic copy; the config rejection maps to its copy.
  • Mounted dashboard-screen test: under ja, an ambiguous sync renders the Japanese securityAgent.mutations.ambiguous copy, the English marker is gone, and both sync controls stay retryable.
  • Android end to end on this VM: local web stack, SECURITY_SYNC_WORKER_URL unreachable so the Worker submission is ambiguous (server confirmed returning the CONFLICT marker); signed in through the real device-auth flow, seeded the account an active GitHub integration and an enabled security-agent config, switched the app language to Japanese, and tapped 今すぐ同期. Baseline bundle (fix stashed) showed the English marker; the fixed bundle shows 「操作の結果を確認できませんでした。再試行する前にセキュリティレビューをご確認ください。」with the reconcile card in Japanese and both controls still enabled. Screenshots: ja-ambiguous-banner-fixed.png, ja-ambiguous-banner-fixed-repro.png (after restore), en-ambiguous-banner.png (English locale), ja-ambiguous-banner-baseline.png (fix stashed, English marker visible).

Merge with main

The branch now merges main. The only conflict was sv.json: the new security keys are kept, and securityAgent.mutations.dismissalInProgress takes main's corrected Swedish wording. After the merge, check:i18n reports all 87 catalogs matching en.json, check:plurals passes, and the mobile typecheck is clean.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Surfaces: Which surfaces should show the translated security ledger copy?
    Answer: All sync/dismiss surfaces
    Confidence: 0.85 · Precedent: Fix the root cause and the class: session 7f3470e0ba6f, "fix class of issue preventing progress"; i18n rule, session dcb3f9170164, "the mobile app always must respect i18n"
  2. Question: Fallback: What should an error whose message is not a known ledger marker render (e.g. server text like 'GitHub App does not have vulnerability_alerts permission')?
    Answer: Generic localized copy
    Confidence: 0.55 · Precedent: Localize every string in the mobile app: session dcb3f9170164, "the mobile app always must respect i18n". The brief also asks for a localized generic error for unknown messages.
  3. Question: Shared copy: The persistence-failure sentence 'We could not record this action. Please try again later.' already exists as prReview.operation.persistenceFailed, and check:i18n rejects a second key holding the same English string. How should the security surface get that copy?
    Answer: Move it to common.*
    Confidence: 0.50 · Precedent: Fix the class rather than the symptom: session 00eb2a1d3e4b, "fix the class". This follows the AGENTS.md convention for copy shared across sections.
  4. Question: Replay marker: The same banner also renders 'This action did not complete. Please try again.' (the replay-of-a-failed-row marker), which is English too but not listed in the brief. Include it?
    Answer: Include it
    Confidence: 0.85 · Precedent: If you see a gap, fix it: session 89824a222897, "if you saw a gap, fix it"
  5. Question: Generic copy: Which copy should the localized generic fallback use?
    Answer: Could not complete this action. Please try again.
    Confidence: 0.60 · Precedent: UX and design quality: make every surface pleasant and consistent (session a247f3c19b1e, owner asked to remove redundant copy); a neutral, actionable key fits both surfaces
  6. Question: Config error: The missing-configuration rejection ('Security service is not configured') also reaches the raw toast today, though the banner already swaps in translated copy. Should the boundary mapping localize it too?
    Answer: Localize it in the mapping
    Confidence: 0.80 · Precedent: Localize every string in the mobile app (session dcb3f9170164: "the mobile app always must respect i18n"); fix the class, not the symptom (session 7f3470e0ba6f: "fix class of issue")
  7. Question: Fallback scope: The generic fallback cannot tell a server English message from an app message that is already translated. Which rule should the mapper use?
    Answer: Generic for every unmapped error
    Confidence: 0.50 · Precedent: Do the least work possible with no drop in quality (session 7f3470e0ba6f: "do the least amount of work possible, with no drop in quality"); retryability stays intact, so the simpler mapper is enough
  8. Question: Kilobot review: PR fix(mobile): localize security agent sync errors #7888 head d509f29 is the merge of main into the branch (fix commits below it were reviewed: ad46af4, 6a22750). Kilobot does not review a merge-commit head, so no 'Kilo Code Review' check exists on d509f29, and the host end check requires a completed kilobot review of the head. The session rule forbids pushing empty commits to trigger reviews; the pr-wait-jit skill documents an empty non-merge commit as the remedy. How should I proceed?
    Answer: Push an empty non-merge commit to request the review
    Confidence: 0.72 · Precedent: Never accept 'blocked' or 'failed' as an end state — session 2f71af3a787e: "a work item must converge"; the pr-wait-jit skill documents this remedy. Fixing the class (merge-commit heads should not need a review, or should trigger one) is a separate workflow fix.

The security operation-ledger markers the server raises for sync and
dismissal failures were rendered verbatim, so a Japanese UI showed the
raw English sentence above an otherwise translated screen.

Map every failure onto catalog copy once at the mutation boundary, the
same way the PR-review operation ledger does: known markers keep their
own sentence, the in-progress duplicate keeps the per-surface copy, and
any other message reads one localized generic sentence. The mapped error
carries the retryability decision taken from the raw error, so the
key-rotation and disabled-control contracts still work after the message
is replaced. The shared persistence-failure sentence moves to
common.actionNotRecorded, which the PR-review surfaces now point at too.

Covered by the ledger unit tests, a mounted dashboard test asserting the
Japanese ambiguous copy renders, and an Android run against the local
stack with an unreachable sync worker.
@iscekic iscekic added drain Work item from the findings drain explorer:android Finding surfaced by the Android explorer labels Oct 17, 2026
@kilo-code-bot

kilo-code-bot Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only change since the last review at ad46af4e4 is an empty review-request commit plus a merge of main; the PR's localization boundary, retryability policy, and durable-draft re-translation are unchanged and both prior suggestions remain resolved.

Assumptions: [INFERENCE] The mobile and i18n suites were not executed in this read-only session (no code changed); catalog coverage and key usage were verified by searching all 87 catalogs and every common.actionNotRecorded call site.

Files Reviewed (104 files)
  • apps/mobile/src/lib/security-agent-ledger.ts
  • apps/mobile/src/lib/hooks/use-security-dismiss-draft.ts
  • apps/mobile/src/lib/hooks/use-security-agent-mutations.ts
  • apps/mobile/src/lib/hooks/use-security-findings.ts
  • apps/mobile/src/lib/hooks/use-security-dismiss-draft.mounted.test.tsx
  • apps/mobile/src/lib/hooks/use-security-agent-mutations.test.ts
  • apps/mobile/src/lib/hooks/use-security-findings.test.ts
  • apps/mobile/src/lib/persist/drafts.ts
  • apps/mobile/src/lib/pr-review/merge/pr-operation-ledger.ts
  • apps/mobile/src/lib/pr-review/mutation-error-display.ts
  • apps/mobile/src/components/security-agent/dismiss-finding-screen.tsx
  • apps/mobile/src/components/security-agent/dismiss-finding-screen.mounted.test.tsx
  • apps/mobile/src/components/security-agent/dashboard-screen.tsx
  • apps/mobile/src/components/security-agent/dashboard-screen.mounted.test.tsx
  • apps/mobile/src/components/security-agent/finding-detail-screen.tsx
  • apps/mobile/src/components/security-agent/finding-detail-screen.mounted.test.tsx
  • apps/mobile/src/components/pr-review/discussion/pr-conversation-comment-composer.tsx
  • apps/mobile/src/components/pr-review/discussion/reply-input.tsx
  • apps/mobile/src/i18n/locales/*.json (87 catalogs: common.actionNotRecorded, securityAgent.mutations.ambiguous/.settleFailed/.replayFailed/.actionFailed/.actionBlocked present in every catalog; the old prReview.operation.persistenceFailed has no remaining references)
Previous Review Summaries (4 snapshots, latest commit ad46af4)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit ad46af4)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The follow-up commit ad46af4e4 resolves both prior suggestions: parseKnownDraft is removed and isSecurityDismissDraft is a pure narrowing guard, with every failure-key reader defaulting a missing legacy field to null.

Files Reviewed (3 files)
  • apps/mobile/src/lib/persist/drafts.ts
  • apps/mobile/src/lib/hooks/use-security-dismiss-draft.ts
  • apps/mobile/src/lib/hooks/use-security-dismiss-draft.mounted.test.tsx

Previous review (commit 6a22750)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The incremental fix is functionally sound: the durable dismiss draft now persists a catalog key and re-translates at render, and the terminal fallback uses non-retry copy. The two findings are maintainability issues in the new default-filling path (a no-op helper and a mutating type predicate), not runtime defects.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 2
Issue Details (click to expand)

SUGGESTION

File Line Issue
apps/mobile/src/lib/persist/drafts.ts 67 parseKnownDraft ignores _isValid and returns its argument unchanged; the default-filling its comment describes happens as a side effect in isSecurityDismissDraft.
apps/mobile/src/lib/hooks/use-security-dismiss-draft.ts 47 isSecurityDismissDraft mutates the caller's value inside a value is T predicate; it works only because loadDraft passes a fresh JSON.parse result.
Files Reviewed (10 files)
  • apps/mobile/src/lib/persist/drafts.ts - 1 issue
  • apps/mobile/src/lib/hooks/use-security-dismiss-draft.ts - 1 issue
  • apps/mobile/src/lib/security-agent-ledger.ts - 0 issues
  • apps/mobile/src/components/security-agent/dashboard-screen.tsx - 0 issues
  • apps/mobile/src/components/security-agent/dismiss-finding-screen.tsx - 0 issues
  • apps/mobile/src/components/security-agent/finding-detail-screen.tsx - 0 issues
  • apps/mobile/src/components/security-agent/dashboard-screen.mounted.test.tsx - 0 issues
  • apps/mobile/src/components/security-agent/dismiss-finding-screen.mounted.test.tsx - 0 issues
  • apps/mobile/src/components/security-agent/finding-detail-screen.mounted.test.tsx - 0 issues
  • apps/mobile/src/lib/hooks/use-security-dismiss-draft.mounted.test.tsx - 0 issues

Earlier findings are resolved at HEAD: the terminal fallback now maps to securityAgent.mutations.actionBlocked, and the durable card re-translates the stored failure through lastErrorKey. Both prior comments are answered and no longer reproduced on changed lines.

Fix these issues in Kilo Cloud

Previous review (commit a0f3594)

Status: 1 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
apps/mobile/src/components/security-agent/dismiss-finding-screen.tsx 110 The durable failure card persists error.message after the boundary mapping has already replaced it with localized catalog copy. SecurityCommandRetryCard renders the stored string verbatim (including after a restart), so a later language change leaves the card in the old language until the draft is retried or discarded. Persisting a stable marker/key and localizing at render would keep the durable card language-correct.
Files Reviewed (4 files)
  • apps/mobile/src/lib/security-agent-ledger.ts
  • apps/mobile/src/lib/hooks/use-security-agent-mutations.test.ts
  • apps/mobile/src/lib/hooks/use-security-findings.test.ts
  • apps/mobile/src/i18n/locales/*.json (87 catalogs)

Fix these issues in Kilo Cloud

Previous review (commit 3fda4e8)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The new localization boundary is sound and the retryability contract is preserved, but the generic fallback replaces terminal server errors with retry copy while the controls are disabled, and the durable dismiss card persists a language snapshot.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/lib/security-agent-ledger.ts 131 Generic fallback renders "Please try again" for non-retryable errors (FORBIDDEN, BAD_REQUEST, PRECONDITION_FAILED, operation_key_reuse_mismatch) whose sync/dismiss controls are disabled, and discards the server's actionable reason

SUGGESTION

File Line Issue
apps/mobile/src/components/security-agent/dismiss-finding-screen.tsx 110 Durable dismiss failure persists an already-localized snapshot, so the dashboard retry card stays in the old language after a language change
Files Reviewed (100 files)
  • apps/mobile/src/lib/security-agent-ledger.ts - 1 issue
  • apps/mobile/src/components/security-agent/dismiss-finding-screen.tsx - 1 issue
  • apps/mobile/src/components/security-agent/dashboard-screen.tsx
  • apps/mobile/src/components/security-agent/dashboard-screen.mounted.test.tsx
  • apps/mobile/src/components/security-agent/dismiss-finding-screen.mounted.test.tsx
  • apps/mobile/src/lib/hooks/use-security-agent-mutations.ts
  • apps/mobile/src/lib/hooks/use-security-agent-mutations.test.ts
  • apps/mobile/src/lib/hooks/use-security-findings.ts
  • apps/mobile/src/lib/hooks/use-security-findings.test.ts
  • apps/mobile/src/lib/pr-review/merge/pr-operation-ledger.ts
  • apps/mobile/src/lib/pr-review/mutation-error-display.ts
  • apps/mobile/src/components/pr-review/discussion/pr-conversation-comment-composer.tsx
  • apps/mobile/src/components/pr-review/discussion/reply-input.tsx
  • apps/mobile/src/i18n/locales/*.json (87 catalogs: uniform key additions, removed key has no remaining references)

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 117K · Output: 10K · Cached: 2M

Review guidance: REVIEW.md from base branch main

A terminal rejection (FORBIDDEN, BAD_REQUEST, PRECONDITION_FAILED) maps
to a fallback sentence that told the reader to try again while the sync
or dismiss controls were disabled. Give the non-retryable fallback its
own catalog sentence that names the fix-first condition instead.

Also store the dismissal failure's retry class on the durable draft, so
the dashboard retry card keeps the Retry affordance decision even when
the stored failure sentence came from a different app language.
The durable dismiss draft stored the failure sentence in the language
that recorded it, so the dashboard retry card kept the old language
after an app-language change. Store the failure's catalog key beside the
sentence (null when the mapper does not recognise it), and re-translate
through that key wherever the draft renders. Drafts recorded before the
field existed read back with the schema's default, so the retry card
keeps showing their stored sentence.
Comment thread apps/mobile/src/lib/persist/drafts.ts Resolved
isSecurityDismissDraft filled the new field's default by mutating the
parsed value inside a narrowing predicate. Drop the mutation and the
no-op parseKnownDraft helper: the guard only narrows, and the consumers
that read the failure key default a missing field to null, which is
exactly what a legacy draft looks like.
@iscekic iscekic added merge:human Substantive functional risk requires human review. merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:human Substantive functional risk requires human review. labels Oct 19, 2026
@iscekic

iscekic commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Re-delivered reports for the same surface (Arabic overview banner, pt-BR headline, Hindi dismissal failure and retry card) are covered by the mapping already on this branch at head ad46af4e40.

  • apps/mobile/src/lib/security-agent-ledger.ts maps the ledger markers at the mutation boundary for both the sync and the dismissal surfaces, so neither the banner, the dismiss sheet, nor the retry cards read the raw marker. The durable dismiss draft stores lastErrorKey next to the sentence, so a card restored after a language change renders from the catalog instead of the write-time language.
  • All 87 catalogs carry securityAgent.mutations.ambiguous, .settleFailed, .replayFailed, .actionFailed, .actionBlocked, and common.actionNotRecorded; ja, ar, pt-BR, and hi were each spot-checked for a localized string.
  • Re-ran the security-agent suites on the VM at this head: 4 files, 94 tests passed (dashboard-screen.mounted, dismiss-finding-screen.mounted, finding-detail-screen.mounted, use-security-agent-mutations).

@iscekic
iscekic enabled auto-merge October 21, 2026 15:24
…ty-sync-errors

# Conflicts:
#	apps/mobile/src/i18n/locales/sv.json
@iscekic
iscekic disabled auto-merge October 21, 2026 17:18
@iscekic iscekic removed the merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. label Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain explorer:android Finding surfaced by the Android explorer merge:bot Bot-owned review; merge when ready once readiness passes and a review approves.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants