Skip to content

ADFA-3689: Terminate project creation on Pebble template errors#1553

Merged
jomen-adfa merged 7 commits into
stagefrom
task/ADFA-3689-pebble-terminate
Jul 21, 2026
Merged

ADFA-3689: Terminate project creation on Pebble template errors#1553
jomen-adfa merged 7 commits into
stagefrom
task/ADFA-3689-pebble-terminate

Conversation

@jomen-adfa

@jomen-adfa jomen-adfa commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes ADFA-3689: Pebble template errors during project creation previously flooded the log and let creation "succeed" with a partially rendered project. They now terminate creation with meaningful feedback.

  • Errors are fatal. Pebble parse, evaluation, and file write/copy failures throw TemplateExecutionException instead of logging and skipping the file. The blanket per-entry catch that swallowed parse errors now rethrows.
  • No poisoned retries. The partial project directory is deleted on failure; previously the projectDir.exists() early-return reported the broken leftover as a successful creation on the next attempt.
  • Meaningful feedback. The user sees the failing template file, line, and cause plus next steps (new template_exec_error_terminated string, English + Indonesian), delivered through the existing ProjectCreationManager -> flashError path instead of "Project created successfully".
  • Warnings stay non-fatal. Identifier-default warnings still complete creation and show the existing post-open notice.

Review by commit

  1. Reformat ZipRecipeExecutor.kt with Spotless — mechanical only; the file predates the ratchet, so touching it forces a full reformat.
  2. Terminate project creation on Pebble template errors — the actual change (~100 lines) plus strings and tests.

Testing

  • New ZipRecipeExecutorTest (Robolectric, real zips built on the fly): parse error terminates + removes project dir; evaluation error ditto; valid template renders; existing project dir untouched.
  • :templates-impl:testV8DebugUnitTest and spotlessCheck green.

🤖 Generated with Claude Code

jomen-adfa and others added 2 commits July 20, 2026 14:13
Mechanical commit, no functional change. The file predates the
Spotless ratchet (space-indented), so touching it forces a full
reformat; done separately to keep the behavioral change reviewable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pebble parse, evaluation, and file write/copy errors previously
logged and continued, producing a partially rendered project that
was reported as created successfully; the leftover directory also
made retries return the broken project as success.

- Introduce TemplateExecutionException; parse/evaluate/write/copy
  failures now abort execution instead of skipping the file
- Delete the partial project directory on failure so a retry does
  not hit the projectDir.exists() early-return
- Surface a user-facing error (failing file, line, cause, and next
  steps) through the existing ProjectCreationManager error path
- Keep identifier-default warnings non-fatal (post-open notice)
- Add ZipRecipeExecutorTest covering parse error, evaluation error,
  success path, and the existing-directory guard

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dfa79423-9a6f-4570-bc0b-186ab2aee16e

📥 Commits

Reviewing files that changed from the base of the PR and between 21ace0a and 425c70d.

📒 Files selected for processing (2)
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt
  • templates-impl/src/test/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutorTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt

📝 Walkthrough
  • Template rendering failures now terminate project creation with detailed error feedback instead of being logged and skipped.
  • Partial project directories are removed after failed rendering; existing project directories remain protected.
  • Added English and Indonesian messages for template failures and cleanup warnings.
  • Keystore-copy failures remain non-fatal after successful rendering.
  • Hardened archive dex-optimization paths against directory traversal using canonical containment checks.
  • Added Robolectric coverage for rendering, Pebble parse/evaluation errors, cleanup, keystore failures, path safety, and existing directories.
  • Reformatted ZipRecipeExecutor.kt with Spotless.

Risks / best-practice considerations

  • Fatal cleanup behavior may remove partially generated files, potentially complicating post-failure diagnosis.
  • The substantial executor refactor could introduce regressions in less-covered template, extension-loading, or filtering paths.
  • Keystore failures continue to produce a project with warnings, so callers must handle hasErrorsWarnings appropriately.

Walkthrough

ZipRecipeExecutor now treats rendering and copy failures as fatal, removes newly created project directories, and throws TemplateExecutionException. Keystore failures remain non-fatal. Localized messages and Robolectric tests cover cleanup, rendering, path safety, and directory preservation.

Changes

Template execution lifecycle

Layer / File(s) Summary
Fatal rendering and cleanup flow
templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt, resources/src/main/res/values*.xml
Rendering failures are wrapped in TemplateExecutionException; partially created projects are deleted, keystore failures remain non-fatal, and termination and cleanup-warning messages are added.
Template parameter and path-safe rendering
templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt
Rendering now centralizes ZIP entry processing, parameter substitution, filtering, path normalization, output containment checks, Pebble evaluation, and extension loading.
Execution behavior tests
templates-impl/src/test/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutorTest.kt
Tests verify successful rendering, cleanup after parse and evaluation failures, keystore failure handling, malicious path rejection, and preservation of existing project directories.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: davidschachteradfa, hal-eisen-adfa

Sequence Diagram(s)

sequenceDiagram
  participant ZipRecipeExecutor
  participant Pebble
  participant ProjectDirectory
  ZipRecipeExecutor->>Pebble: render template entry
  Pebble-->>ZipRecipeExecutor: rendering failure
  ZipRecipeExecutor->>ProjectDirectory: delete partially created project
  ZipRecipeExecutor-->>ZipRecipeExecutor: throw TemplateExecutionException
Loading

Poem

I’m a rabbit guarding templates tonight,
Pebble errors take a cleanup flight.
Half-built folders hop away,
Good files bloom by break of day.
Exceptions thump their tidy beat!

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: terminating project creation on Pebble template errors.
Description check ✅ Passed The description matches the changeset and accurately describes the fatal template-error handling and cleanup behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/ADFA-3689-pebble-terminate

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt (1)

546-549: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Non-fatal extension-load failure can surface later as a misleading fatal error.

loadExtensionFromArchive logs and returns emptyList() on failure. If a template depends on functions/filters provided by that extension, rendering continues without it and (with strictVariables(true)) later fails in getTemplate/evaluate, producing a fatal parse/evaluation error that doesn't mention the real cause (the extension failed to load). Consider whether extension-load failure should also be fatal, or at least reflected in the eventual error message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt`
around lines 546 - 549, Update loadExtensionFromArchive so extraction failures
do not silently return emptyList() when the extension is required for template
rendering. Propagate the original exception or record it in a failure state
consumed by getTemplate/evaluate, ensuring the eventual error identifies the
extension-load failure instead of only reporting a misleading parse or
evaluation error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt`:
- Around line 66-79: Handle failures from keystore(executor) separately from the
fatal renderProject(ctx, projectDir) path in the surrounding execution flow.
Preserve cleanup and TemplateExecutionException behavior for rendering failures,
but catch keystore copy/property errors as non-fatal, issue the existing
warning, and set hasErrorsWarnings so project creation still succeeds without
release keystore artifacts.
- Around line 559-563: Validate templateRef.path/basePath before constructing
optimizedDir, preventing absolute paths, traversal segments, or separators from
escaping Environment.TEMPLATES_DIR. Reuse the canonical containment-check
approach already applied to outFile, and reject invalid paths before any
filesystem access.

---

Nitpick comments:
In
`@templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt`:
- Around line 546-549: Update loadExtensionFromArchive so extraction failures do
not silently return emptyList() when the extension is required for template
rendering. Propagate the original exception or record it in a failure state
consumed by getTemplate/evaluate, ensuring the eventual error identifies the
extension-load failure instead of only reporting a misleading parse or
evaluation error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0480472a-df1e-4194-99e2-50719455ce8f

📥 Commits

Reviewing files that changed from the base of the PR and between 304a46c and 6ca74c2.

📒 Files selected for processing (4)
  • resources/src/main/res/values-in-rID/strings.xml
  • resources/src/main/res/values/strings.xml
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt
  • templates-impl/src/test/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutorTest.kt

@jomen-adfa
jomen-adfa requested a review from a team July 20, 2026 06:38
Review feedback (CodeRabbit): the release keystore is auxiliary, so a
copy failure now warns and sets hasErrorsWarnings instead of deleting
the fully rendered project. Test added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt (1)

66-78: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Catch narrowly and rethrow CancellationException.

Broad exception catches that do not rethrow CancellationException swallow coroutine cancellation, breaking the coroutine hierarchy. Based on learnings and as per coding guidelines, prefer narrow exception handling and explicitly rethrow CancellationException.

  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt#L66-L78: Insert if (e is CancellationException) throw e at the beginning of the catch block.
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt#L80-L86: Change the broad catch (e: Exception) to catch (e: IOException) since keystore() only performs file operations.
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt#L530-L537: Update the wrap extension to return Exception (rather than TemplateExecutionException explicitly) and add if (this is CancellationException) return this so all throw e.wrap(...) call sites automatically rethrow cancellations without needing individual modifications.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt`
around lines 66 - 78, Preserve coroutine cancellation and narrow exception
handling in ZipRecipeExecutor.kt: at lines 66-78, immediately rethrow
CancellationException before cleanup or wrapping; at lines 80-86, change the
keystore() catch from Exception to IOException; at lines 530-537, make wrap
return Exception and return CancellationException unchanged so all throw
e.wrap(...) call sites propagate cancellation automatically.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt`:
- Around line 66-78: Preserve coroutine cancellation and narrow exception
handling in ZipRecipeExecutor.kt: at lines 66-78, immediately rethrow
CancellationException before cleanup or wrapping; at lines 80-86, change the
keystore() catch from Exception to IOException; at lines 530-537, make wrap
return Exception and return CancellationException unchanged so all throw
e.wrap(...) call sites propagate cancellation automatically.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 79462c3e-944a-4ee9-95f1-21bdadae70ea

📥 Commits

Reviewing files that changed from the base of the PR and between 6ca74c2 and 21ace0a.

📒 Files selected for processing (2)
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutor.kt
  • templates-impl/src/test/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutorTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • templates-impl/src/test/java/com/itsaky/androidide/templates/impl/zip/ZipRecipeExecutorTest.kt

jomen-adfa and others added 2 commits July 20, 2026 15:04
Review feedback (CodeRabbit): basePath comes from the archive's
templates.json, so a crafted path could escape TEMPLATES_DIR via the
dex-opt directory. Apply the same canonical containment check used
for outFile and skip extension loading on violation. Test added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review feedback: extract processEntry / renderTemplateEntry /
copyBinaryEntry from the entry loop. No behavior change; the loop
body now reads skip checks -> path containment -> dispatch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jomen-adfa
jomen-adfa requested a review from jatezzz July 20, 2026 13:47
@jomen-adfa
jomen-adfa merged commit 1199158 into stage Jul 21, 2026
4 checks passed
@jomen-adfa
jomen-adfa deleted the task/ADFA-3689-pebble-terminate branch July 21, 2026 12:33
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.

2 participants