Skip to content

fix: keep runtime error text on device and copy unsupported attachments into the workspace - #391

Merged
yuga-hashimoto merged 2 commits into
mainfrom
fix-issues-386-387
Oct 18, 2026
Merged

yuga-hashimoto merged 2 commits into
mainfrom
fix-issues-386-387

Conversation

@yuga-hashimoto

Copy link
Copy Markdown
Owner

Summary

Two fixes, one per issue.

#386 — keep runtime error text on device, and make crash reporting opt-in

  • Session failures are no longer reported to Crashlytics as the provider's raw error text. They are sent as a fixed category only: Runtime session error: <runtime>/<errorType>, where errorType is accepted only if it is identifier-shaped (ProviderAuthError, MessageAbortedError, …) and otherwise becomes unknown. The free-form message stays on-device, in the notification.
  • Crashlytics now shares the "Anonymous usage analytics & crash reports" opt-in (CrashReporter.setEnabled), is off by default in the manifest (firebase_crashlytics_collection_enabled=false), and stays off in debug builds regardless of the setting.
  • Advertising-ID collection is disabled (google_analytics_adid_collection_enabled=false) and the AD_ID permission is removed from the merged manifest.
  • SecretRedaction now also strips the credential behind a Basic Authorization header, every Cookie pair, quoted JSON credential fields, env-style key names (ANTHROPIC_API_KEY, refresh_token) and URL userinfo.
  • The bundled EN/JA legal docs were refreshed to match.

#387 — copy unsupported attachments into the workspace

Providers reject file parts outside images/PDF ('file part media type application/zip' functionality not supported), so a zip could not be sent at all. When the composer attaches such a MIME and the current runtime is the on-device one (BackendKind.LOCAL, whose /workspace is a real local mount), the file is copied to <workspace>/.andcode-attachments/ and referenced by path in the prompt text instead. Images/PDFs still go as file parts, and a remote runtime is unaffected — it still receives the attachment directly, since there is no local mount to copy into. hostWorkspace re-checks the resolved path so a .. segment cannot escape the workspace root.

Notes / trade-offs

  • The workspace reference is part of the prompt text, so it is also visible in the user's own bubble and in the transcript the runtime stores. Hiding it on screen only would not survive a transcript reload, so it is kept as-is.
  • text/plain is treated as non-sendable and therefore path-referenced, not inlined.
  • Checked locally (on-device PRoot): detekt, spotlessCheck, :app:testGithubDebugUnitTest (1350 tests), :app:lintGithubDebug, :app:compileFdroidDebugKotlin, and the i18n key-parity check. CI builds/assembles the rest.

Closes #386
Closes #387

Pre-PR review

判定: APPROVE

ブロッカー

  • なし

提案(非ブロッキング)

  • app/src/main/java/com/yugahashimoto/andcode/ui/AndCodeApp.kt:544 / app/src/main/java/com/yugahashimoto/andcode/feature/chat/ChatViewModel.kt:1320 — ワークスペース参照ノートはモデルへのプロンプト断片ですが、同時にユーザー自身のメッセージバブル本文(userMessage.parts/outgoingText)にもそのまま表示されます。translatable="false" のため非英語ロケールでも英語表示になります。プロンプトとして英語固定なのは repo ルール通りで妥当ですが、「モデルに送る本文」と「画面に出す本文」を分けたい場合は分離を検討してください(前回承認時の既存挙動なので情報提供)。
  • app/src/main/java/com/yugahashimoto/andcode/runtime/local/AttachmentImporter.kt:67 — コピー先が <workspace>/.andcode-attachments/<name> 直下で、Antigravity の .../<session>/<turn>/ のような階層がありません。添付のたびにファイルが蓄積し、リポジトリの未追跡ファイルとして見え続けます。セッション別サブディレクトリかクリーンアップを検討してもよいかもしれません(非ブロッキング)。
  • app/src/main/java/com/yugahashimoto/andcode/runtime/local/AttachmentSupport.kt:53 — guestWorkspacePath はリテラル "/" で比較し、hostWorkspace は File.separator を使っています。Android では等価なので実害はありませんが、軽微な不統一です。

チェック済み項目

  • 全28ファイルの git diff origin/main...HEAD を網羅確認(privacy ドキュメント・assets・manifest・両 Flavor の CrashReporter・API モデル・サニタイザ・redaction・repository・DI・ViewModel・runtime・UI・テスト)。
  • 新規ガード(今回の再レビュー対象): hostWorkspace の canonical 検証を手計算で検証。/workspace/../etc・/workspace/proj/../../etc は candidate.canonicalPath が root 外になり null(attachment は従来通り送信にフォールバック)。/workspace・/workspace/proj・/workspace/proj/ は assertEquals を満たす。canonicalPath が例外を投げる場合は runCatching{}.getOrNull() → null(fail-safe)。relative は trim('/') で先頭 / を除去するため絶対パス注入は不可。ガードは妥当。
  • i18n: analytics_row(テキスト変更)と新規 chat_workspace_attachment_note はいずれも translatable="false" で values/strings.xml のみに存在 → i18n-check.yml(non-translatable 除外ロジック)は通る。他ロケールに重複定義なし。ハードコード文字列なし(ViewModel は英語デフォルト注入、ViewModelModule/AndCodeApp の両構築経路で getString を注入済み)。
  • クラッシュレポート opt-in: github/fdroid 両 CrashReporter に setEnabled・recordRuntimeSessionError を追加。install() は常に false、setEnabled は enabled && !DEBUG。AndCodeApplication で install()→setEnabled(settings.analyticsEnabled) の順を確認。AppPreferencesRepository.setAnalyticsEnabled を更新。
  • manifest: xmlns:tools 宣言済みで AD_ID の tools:node="remove"、firebase_crashlytics_collection_enabled=false、google_analytics_adid_collection_enabled=false を追加。妥当。
  • 生エラー本文の非送信: RuntimeActivityRepository の onSessionError を (sessionId, message, errorType, runtimeId) に変更し全呼び出し元(Application/AppModule/テスト)を更新。CrashReportSanitizer.errorCategory は識別子形状のみ採用(SAFE_TYPE 不一致は unknown)。recordException を生メッセージで呼ぶ箇所が他に無いことを確認。
  • 直列化への影響: PromptAttachment.workspacePath を追加したが、API 送信ボディは OpenCodeApiClient/OpenCodeV2ApiClient で buildJsonObject により mime/filename/url のみを手組みするため、workspacePath がワイヤに出ない(encodeDefaults=true でも無関係)。退行なし。ワークスペース添付は送信前に partition で除外され modelAttachments のみ送信。
  • SecretRedaction 強化: 新パターン(Basic 認証後続、Cookie 全体、汎用 key:value/引用符付き JSON、URL userinfo)を既存・追加テストの入力で手計算し、既存テストの期待(<redacted> 含有、通常文非改変、クエリ除去)を壊さないことを確認。破滅的バックトラックの懸念なし。
  • テスト/静的解析: 追加テストはリポジトリのテストパターンに沿っており、ChatViewModel(backend, workspaceAttachmentNoteTemplate=...) の名前付き引数はコンストラクタ定義(最後のデフォルト引数)と整合。変更ファイルに行長 >140 なし(.editorconfig で ktlint max-line-length は無効、detekt 140)。hostWorkspace の return は 4(detekt ReturnCount max=4 以内)、新規の関数数・複雑度も閾値内。
  • 作業ツリーはクリーン。シークレットの混入なし。

Providers reject file parts outside images/PDF (for example
`'file part media type application/zip' functionality not supported`), so a
zip or other binary attachment could not be sent at all.

When the composer attaches a MIME a runtime cannot carry as a file part, copy
it into the workspace under `.andcode-attachments/` and reference the path in
the prompt text instead. Images and PDFs keep going as file parts.

This only applies to the on-device runtime, whose `/workspace` is a local
mount (`BackendKind.LOCAL`); a remote runtime is unaffected and still receives
the attachment directly, since there is no local mount to copy into.

PromptAttachment gains a `workspacePath`; ChatViewModel partitions attachments
into model parts and workspace references so the on-disk copy is never sent.
The `.andcode-attachments` directory name is now shared with Antigravity's
inline-attachment path.
…s on opt-in

A runtime/provider session failure was reported to Crashlytics as the raw
provider message wrapped in an exception. That text is free-form - it can
quote prompts, model output, file paths and hosts - and Crashlytics shares
its collection switch with Analytics, which was opt-in. A user who never
enabled analytics still had crash reports collected in release builds.

- Report a session failure as a fixed category only: `Runtime session error:
  <runtime>/<errorType>`, where errorType is accepted only if
  identifier-shaped (ProviderAuthError, ...) and otherwise becomes `unknown`.
  The raw message stays on-device in the notification.
- Disable Crashlytics collection by default in the manifest
  (firebase_crashlytics_collection_enabled=false) and follow the
  analytics/crash opt-in via CrashReporter.setEnabled(); debug builds stay
  off regardless.
- Turn off advertising-ID collection and remove the AD_ID permission.
- Broaden SecretRedaction: the credential behind a Basic Authorization header,
  every Cookie pair, quoted JSON credential fields, env-style key names
  (ANTHROPIC_API_KEY, refresh_token) and URL userinfo are now redacted.
- Refresh the bundled legal docs (EN and JA) to match.
@yuga-hashimoto
yuga-hashimoto merged commit e43c5ea into main Oct 18, 2026
5 checks passed
@yuga-hashimoto
yuga-hashimoto deleted the fix-issues-386-387 branch October 18, 2026 03:08
yuga-hashimoto pushed a commit that referenced this pull request Oct 18, 2026
Conflicts with #391 (workspace attachments) in ChatViewModel and
ChatHomeScreen, resolved by keeping both sides:
- ChatViewModel constructor: keep workspaceAttachmentNoteTemplate and the
  Codex fast-mode parameters.
- Prompt request: send the Codex service tier together with modelAttachments.
  #391 split pendingAttachments into modelAttachments and
  workspaceAttachments, and #389 still passed pendingAttachments to the
  request; pendingAttachments stays for the optimistic message and previews.
- ChatHomeScreen imports: keep Role and the semantics imports.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xw2ucui6t6dQdyGRrnCbHb
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.

Zip file not supported Privacy: Crashlytics receives raw runtime/provider error text (prompts, paths, hosts) and cannot be turned off

1 participant