Skip to content

ADFA-2446 | Improve terminal bootstrap install resilience and user messaging#812

Merged
jatezzz merged 1 commit intostagefrom
fix/ADFA-2446-terminal-bootstrap-retry
Jan 13, 2026
Merged

ADFA-2446 | Improve terminal bootstrap install resilience and user messaging#812
jatezzz merged 1 commit intostagefrom
fix/ADFA-2446-terminal-bootstrap-retry

Conversation

@jatezzz
Copy link
Collaborator

@jatezzz jatezzz commented Jan 9, 2026

Description

This PR hardens the terminal bootstrap installation by retrying once when the temporary bootstrap zip disappears during channel open (likely due to cache/staging cleanup or race conditions). If the issue occurs twice, we fail fast with a localized, user-friendly message instead of a generic error, and propagate the exception message through the existing install progress reporting.

Details

  • Added a localized string for terminal setup failure guidance (low storage / cache cleared).
  • Introduced small helpers to reduce duplication: temp zip creation + channel usage, brotli asset write, and a single-retry wrapper for NoSuchFileException.
  • Updated AssetsInstallationHelper.install() to forward the underlying failure message to progress output and Result.Failure for better UX and debugging.
  • Cleaned some unrelated code blocks

Ticket

ADFA-2446

Observation

The installer runs multiple asset installs concurrently against a shared staging directory, so transient NoSuchFileException can occur if the temp file or parent directory is removed between write and channel open. The retry is intentionally bounded to a single attempt to avoid masking persistent storage issues.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Release Notes

Features & Improvements

  • Enhanced resilience for terminal bootstrap installation: Implemented single-retry mechanism when temporary bootstrap zip files disappear during channel open, addressing transient failures caused by cache/staging cleanup or race conditions
  • User-friendly error messaging: Added localized strings guiding users on terminal setup failures, specifically mentioning low storage or cache cleared scenarios
  • Improved error diagnostics: Propagated underlying exception messages through install progress reporting and Result.Failure to enhance debugging and user understanding

Code Quality & Maintenance

  • Reduced code duplication through introduction of three new internal utility helpers:
    • withTempZipChannel(): Encapsulates temporary ZIP creation and channel management
    • writeBrotliAssetToPath(): Handles Brotli asset decompression and writing
    • retryOnceOnNoSuchFile(): Single-retry wrapper for transient NoSuchFileException handling
  • Refactored asset installers: Updated BundledAssetsInstaller.kt and SplitAssetsInstaller.kt to use new helper utilities for cleaner, more maintainable code
  • Enhanced error handling: More descriptive IOException messages when terminal installation encounters Interactive mode or secondary user restrictions

Risk Considerations & Best Practices Notes

  • ⚠️ Shared staging directory: The solution relies on proper staging directory management across concurrent asset installations; ensure filesystem permissions and cleanup routines are validated in integration testing
  • ⚠️ Single-retry design: Retry is intentionally limited to avoid masking persistent storage issues; monitor error logs for patterns of repeated failures indicating systemic problems
  • Defensive design: Staging directory is pre-created on first failure to handle edge cases where parent directories may be missing
  • No public API changes: All modifications are internal utility functions; existing exported entities remain structurally unchanged

Walkthrough

This change refactors asset installation error handling and extraction flows. AssetsInstallationHelper now derives dynamic error messages from exceptions. BundledAssetsInstaller and SplitAssetsInstaller are refactored to use new channel-based utilities with retry logic. New string resources added for terminal installation errors. InstallerIoUtils introduces helpers for temporary ZIP channel handling, Brotli asset writing, and retry-on-failure patterns.

Changes

Cohort / File(s) Summary
Error Handling & String Resources
app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt, resources/src/main/res/values/strings.xml
Enhanced error reporting: dynamically derives messages from exceptions and propagates alongside failure. Added localized strings for terminal installation failures (low storage and secondary user constraints).
Utility Infrastructure
app/src/main/java/com/itsaky/androidide/utils/InstallerIoUtils.kt
New file with three internal utilities: withTempZipChannel for managing temporary ZIP files with channels, writeBrotliAssetToPath for decompressing and writing Brotli assets, and retryOnceOnNoSuchFile for retry-on-failure patterns.
Asset Installer Refactoring
app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt, app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt
Replaced direct asset streaming with channel-based flows using new utilities. Integrated retry mechanism and improved error mapping from TerminalInstaller results to contextual exceptions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • jomen-adfa
  • itsaky-adfa

Poem

🐰 Assets flow through channels bright,
Brotli streams compressed just right,
Retry once when paths align,
Temp files dance, then say goodbye,
Installation errors now refine! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly reflects the main objectives: improving terminal bootstrap install resilience through retry logic and enhancing user messaging with localized error strings.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the retry mechanism, localized messaging, and the rationale for bounded retry logic in concurrent asset installation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
editor/src/main/java/com/itsaky/androidide/editor/language/newline/BracketsNewlineHandler.kt (1)

51-51: Unused variable after refactor.

The variable count is declared but never used after the changes to lines 56-57. This appears to be leftover from the previous implementation where it was part of the coerceIn calculation.

🧹 Proposed fix to remove dead code
-    val count = 100_000_000
-
     val advanceBefore: Int = getIndentAdvance(beforeText)
🤖 Fix all issues with AI agents
In @app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt:
- Around line 240-255: The retryOnceOnNoSuchFile function is catching
kotlin.io.NoSuchFileException by default; add an explicit import for
java.nio.file.NoSuchFileException so the catch clauses refer to the java.nio
variant thrown by Files.createDirectories(); update the file-level imports to
include java.nio.file.NoSuchFileException and keep the function signature and
catch clauses as-is so the retry logic will catch the correct exception type.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b877ff and e768712.

📒 Files selected for processing (5)
  • app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt
  • app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt
  • editor/src/main/java/com/itsaky/androidide/editor/language/newline/BracketsNewlineHandler.kt
  • editor/src/main/java/com/itsaky/androidide/editor/language/newline/TSBracketsHandler.kt
  • resources/src/main/res/values/strings.xml
🔇 Additional comments (7)
editor/src/main/java/com/itsaky/androidide/editor/language/newline/TSBracketsHandler.kt (1)

42-43: LGTM!

The change to dynamically compute indentation by counting leading spaces in the current line is a clear improvement over a hard-coded constant. The coerceIn bounds check ensures safety against excessively large values.

resources/src/main/res/values/strings.xml (1)

51-51: LGTM!

The new localized string provides a clear, actionable message for users when terminal setup fails due to storage issues. It appropriately suggests the likely causes (low storage or cache cleared) and offers guidance (free up storage).

editor/src/main/java/com/itsaky/androidide/editor/language/newline/BracketsNewlineHandler.kt (1)

56-64: LGTM!

The refactored bounds checking directly on advanceBefore and advanceAfter is cleaner. The char literal for newline is a minor but appropriate optimization.

app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt (1)

76-80: LGTM!

Good improvement to error handling: extracting the actual exception message, logging with the exception for stack traces, and propagating the specific error through both progress and the Failure result. This aligns well with the PR objective of better UX and debugging.

app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt (3)

72-95: LGTM!

The retry logic is well-structured: on first NoSuchFileException, recreate the staging directory (addressing the race condition), and on second failure, throw with the user-friendly localized message. The use of withTempZipChannel cleanly abstracts the temp file lifecycle.


209-224: LGTM!

Clean helper function with proper resource management. The finally block ensures the temp file is cleaned up regardless of success or failure. The inline modifier is appropriate for this small utility.


226-238: LGTM!

Clean helper for Brotli asset extraction with proper nested resource management using use{} blocks.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e768712 and d31a2a0.

📒 Files selected for processing (1)
  • app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt
🔇 Additional comments (4)
app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt (4)

210-225: Excellent abstraction with proper resource management.

The withTempZipChannel helper cleanly encapsulates the temp file lifecycle:

  • Separates file creation from usage
  • Guarantees cleanup via finally block
  • Properly manages the channel lifetime with use {}

The inline modifier is appropriate here to eliminate lambda allocation overhead.


227-239: Clean Brotli extraction helper.

The function correctly handles Brotli decompression with proper resource management. All streams are closed automatically via use {} blocks.


241-256: Robust single-retry pattern with excellent type safety.

The implementation is well-crafted:

  • The Nothing return type on onSecondFailure enforces that it must throw an exception, preventing accidental silent failures
  • Scoped specifically to NoSuchFileException, allowing other exceptions to propagate immediately
  • Single retry limit prevents masking persistent storage or permission issues
  • Default empty onFirstFailure provides flexibility

This reusable helper elegantly addresses the race condition described in the PR objectives.


77-92: Well-designed retry mechanism for transient file system issues.

The retry logic correctly handles the scenario where the staging directory may not exist when the installation process runs. The approach is sound:

  • First failure: pre-create the staging directory to eliminate missing parent as a cause
  • Second failure: fail fast with a localized, user-friendly message ("Terminal setup failed. Your device may be low on storage or the app cache was cleared during setup. Please free up storage and try again.")

The helper functions are properly implemented: withTempZipChannel ensures correct resource cleanup via try-finally even when exceptions occur, writeBrotliAssetToPath properly manages nested streams, and retryOnceOnNoSuchFile correctly implements the retry logic with exception-specific handling.

@jatezzz jatezzz force-pushed the fix/ADFA-2446-terminal-bootstrap-retry branch from ac4cfe4 to a9fea41 Compare January 12, 2026 13:51
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
editor/src/main/java/com/itsaky/androidide/editor/language/newline/BracketsNewlineHandler.kt (1)

51-57: Remove unused count variable.

The count variable declared on line 51 is now dead code since it's no longer used in the safeIndentBefore and safeIndentAfter calculations. This should be removed to avoid confusion.

🧹 Suggested cleanup
  private fun handleNewline(
    beforeText: String?,
    afterText: String?,
    tabSize: Int
  ): NewlineHandleResult {
-   val count = 100_000_000
-
    val advanceBefore: Int = getIndentAdvance(beforeText)
    val advanceAfter: Int = getIndentAdvance(afterText)

    val safeIndentBefore = advanceBefore.coerceIn(0, maxIndentColumns)
    val safeIndentAfter = advanceAfter.coerceIn(0, maxIndentColumns)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d31a2a0 and 05d8273.

📒 Files selected for processing (5)
  • app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt
  • app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt
  • editor/src/main/java/com/itsaky/androidide/editor/language/newline/BracketsNewlineHandler.kt
  • editor/src/main/java/com/itsaky/androidide/editor/language/newline/TSBracketsHandler.kt
  • resources/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • editor/src/main/java/com/itsaky/androidide/editor/language/newline/TSBracketsHandler.kt
  • resources/src/main/res/values/strings.xml
⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build Universal APK
🔇 Additional comments (7)
editor/src/main/java/com/itsaky/androidide/editor/language/newline/BracketsNewlineHandler.kt (1)

61-61: LGTM!

Using a char literal '\n' instead of a string literal "\n" for StringBuilder.append() is a good micro-optimization.

app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt (1)

75-81: LGTM! Improved error propagation.

The changes correctly derive the error message with a fallback, log the full exception for debugging, and forward the meaningful message to both the progress consumer and the Result.Failure. This aligns with the PR objective to improve user-facing error messaging.

app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt (5)

221-236: LGTM! Proper resource management.

The withTempZipChannel helper correctly handles the temp file lifecycle: creation, usage via channel with use, and cleanup in finally. The inline modifier is appropriate for the lambda parameters.


238-250: LGTM!

Clean helper with proper stream chaining and resource cleanup via nested use blocks.


252-267: LGTM! Well-designed retry mechanism.

The Nothing return type for onSecondFailure enforces that it must throw, ensuring type safety. The single-retry limit is appropriate per the PR rationale to avoid masking persistent storage issues.


73-108: LGTM! Robust bootstrap installation with clear failure messaging.

The implementation correctly addresses the transient NoSuchFileException scenario described in the PR objectives. The retry recreates the staging directory on first failure, and the second failure surfaces a user-friendly localized message. The result pattern matching properly converts each InstallResult variant to an appropriate exception type.


94-107: No action needed. The when expression is exhaustive and correctly handles all variants of the sealed InstallResult interface. The compiler enforces exhaustiveness checking for sealed types, so no else branch is required.

Likely an incorrect or invalid review comment.

@jatezzz jatezzz requested a review from alome007 January 12, 2026 14:22
@jatezzz jatezzz requested a review from itsaky-adfa January 12, 2026 15:42
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt:
- Around line 71-92: The onSecondFailure lambda passed to retryOnceOnNoSuchFile
in SplitAssetsInstaller currently does return@withContext and swallows the
error; change it to throw an exception (e.g., throw IOException("Failed to open
temporary bootstrap zip after retry", e2)) so the failure is propagated from
doInstall like BundledAssetsInstaller does; update the onSecondFailure callback
in the retryOnceOnNoSuchFile call that wraps
withTempZipChannel/TerminalInstaller.installIfNeeded to rethrow the
NoSuchFileException (or wrap it) instead of returning, ensuring callers of
doInstall see the error.
🧹 Nitpick comments (1)
app/src/main/java/com/itsaky/androidide/utils/InstallerIoUtils.kt (1)

41-56: Effective single-retry pattern with type-safe failure handling.

The Nothing return type on onSecondFailure is a clever design choice that forces callers to either throw an exception or enter an infinite loop (the latter being unlikely). This prevents accidental silent failures.

Minor observation: the first NoSuchFileException is discarded (_), which is acceptable since onFirstFailure is meant for recovery actions, not error inspection. If future debugging needs arise, consider logging the first exception path before retry.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05d8273 and f276386.

📒 Files selected for processing (4)
  • app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt
  • app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt
  • app/src/main/java/com/itsaky/androidide/utils/InstallerIoUtils.kt
  • resources/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (1)
  • resources/src/main/res/values/strings.xml
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt (1)
app/src/main/java/com/itsaky/androidide/utils/InstallerIoUtils.kt (3)
  • retryOnceOnNoSuchFile (41-56)
  • withTempZipChannel (10-25)
  • writeBrotliAssetToPath (27-39)
app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt (1)
app/src/main/java/com/itsaky/androidide/utils/InstallerIoUtils.kt (2)
  • retryOnceOnNoSuchFile (41-56)
  • withTempZipChannel (10-25)
🔇 Additional comments (8)
app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt (2)

9-10: LGTM!

New imports correctly reference the utility functions from InstallerIoUtils.kt.


94-98: Result handling after retry looks correct.

The result check logs an error on non-success, which is appropriate. However, note that if onSecondFailure is fixed to throw, this block will only execute on success or first-attempt failures that recovered.

app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt (3)

8-13: LGTM!

New imports are properly organized and support the new retry mechanism and error handling.

Also applies to: 26-26


78-93: Well-implemented retry mechanism with proper error propagation.

The onSecondFailure correctly throws an IOException with a localized message, satisfying the Nothing return type contract. The staging directory creation on first failure is a sensible recovery strategy for transient NoSuchFileException issues.


95-108: Exhaustive result handling with appropriate exception types.

The when expression correctly handles all InstallResult variants:

  • Success: no-op (correct)
  • Interactive: throws IOException with title and message
  • IsSecondaryUser: throws IOException with localized string
  • NotInstalled: throws IllegalStateException

This ensures errors propagate to callers for proper handling.

app/src/main/java/com/itsaky/androidide/utils/InstallerIoUtils.kt (3)

1-9: LGTM!

Package declaration and imports are appropriate for the utility functions defined.


10-25: Well-designed temp file lifecycle management.

The try-finally ensures cleanup even if writeTo or useChannel throws. The inline modifier is appropriate for a function accepting lambdas to avoid allocation overhead.

One consideration: if Files.createTempFile throws (e.g., stagingDir doesn't exist), the exception propagates directly. This is the intended behavior since the calling code wraps this in retryOnceOnNoSuchFile to handle that case.


27-39: Clean Brotli decompression helper.

Proper use of nested .use {} blocks ensures all streams are closed in the correct order (inner-to-outer). The function is straightforward and does one thing well.

…lure & extracting shared functions to a Util file

wip
@jatezzz jatezzz force-pushed the fix/ADFA-2446-terminal-bootstrap-retry branch from 33bc6c7 to b353f64 Compare January 12, 2026 16:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f276386 and b353f64.

📒 Files selected for processing (5)
  • app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt
  • app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt
  • app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt
  • app/src/main/java/com/itsaky/androidide/utils/InstallerIoUtils.kt
  • resources/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • resources/src/main/res/values/strings.xml
  • app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt (1)
app/src/main/java/com/itsaky/androidide/utils/InstallerIoUtils.kt (2)
  • retryOnceOnNoSuchFile (41-56)
  • withTempZipChannel (10-25)
app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt (1)
app/src/main/java/com/itsaky/androidide/utils/InstallerIoUtils.kt (3)
  • retryOnceOnNoSuchFile (41-56)
  • withTempZipChannel (10-25)
  • writeBrotliAssetToPath (27-39)
⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build Universal APK
🔇 Additional comments (5)
app/src/main/java/com/itsaky/androidide/utils/InstallerIoUtils.kt (3)

10-25: Well-designed temp file lifecycle management.

The withTempZipChannel correctly handles the temp file cleanup in finally, ensuring deletion even if writeTo or useChannel throws. The .use {} block properly closes the channel.


27-39: LGTM!

Clean decompression pipeline with proper resource management through nested .use {} blocks.


41-56: Solid retry utility with correct Nothing return type.

The onSecondFailure: (NoSuchFileException) -> Nothing signature correctly enforces that callers must terminate (throw/exit) on second failure. The intentional suppression of the first exception aligns with the PR rationale of handling transient failures.

app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt (2)

78-93: Good retry implementation with proper error propagation.

The retry logic correctly creates the staging directory on first failure and throws a user-friendly IOException with localized message on second failure. The withTempZipChannel usage is clean.


95-108: Comprehensive error handling for all result variants.

Exhaustive when block with appropriate exception types: IOException for user-actionable errors and IllegalStateException for unexpected states.

@jatezzz jatezzz merged commit 658f77a into stage Jan 13, 2026
2 checks passed
@jatezzz jatezzz deleted the fix/ADFA-2446-terminal-bootstrap-retry branch January 13, 2026 14:29
This was referenced Jan 23, 2026
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.

4 participants