Skip to content

feat(builds): Pull + Commit + Push Builds #6498

Merged
Aradhya-Tripathi merged 28 commits into
developfrom
quick-builds
Jun 2, 2026
Merged

feat(builds): Pull + Commit + Push Builds #6498
Aradhya-Tripathi merged 28 commits into
developfrom
quick-builds

Conversation

@Aradhya-Tripathi
Copy link
Copy Markdown
Contributor

@Aradhya-Tripathi Aradhya-Tripathi commented May 24, 2026

Goes with frappe/agent#527

Greptile Summary

This PR introduces "patch builds" — a fast deploy path that pulls the latest app code into an existing Docker image (pull + commit + push) instead of rebuilding from scratch. It wires the feature through the full stack: a new trigger_patch_deploy flow on DeployCandidate, a run_patch_build path in DeployCandidateBuild, output parsing via PatchBuildOutputParser, release-pipeline support via a new initiate_patch_deploy task, and a "Deploy as Patch" button in the dashboard dialog.

  • can_run_patch_build guards the feature with thorough checks (same apps, sources, dependencies, packages, env vars, active benches for every required platform) and is backed by a comprehensive test suite.
  • _create_platform_patch_build_if_required_and_deploy chains a second-platform patch build automatically when a dual-platform candidate is needed, mirroring how regular builds handle arm64/x86_64 pairs.
  • The PatchBuildOutputParser syncs step statuses from agent job steps rather than streaming Docker output, and the UploadStepUpdater already guards against a missing upload step for no_push builds.

Confidence Score: 4/5

Safe to merge after fixing the suspended-builds error path in bench_update.py — the current code silently discards the actionable message and returns a 500 to the caller.

The core patch-build machinery is well-structured and the guard logic is thoroughly tested. The one issue that stands out is in bench_update.py: when builds are suspended and the old (non-workflow) deploy path is used with trigger_patch_deploy=True, the informative suspension message is raised as a plain Python Exception, which Frappe's API layer swallows and converts to a generic 500. The user never sees the real reason.

press/press/doctype/bench_update/bench_update.py — the suspended-builds error case uses raise Exception instead of frappe.throw, losing the message on the non-new-deploy-flow path.

Important Files Changed

Filename Overview
press/press/doctype/deploy_candidate_build/deploy_candidate_build.py Core patch build logic: adds run_patch_build, send_patch_build_instructions, _sync_patch_build_step_statuses, and _create_platform_patch_build_if_required_and_deploy; N+1 DB queries inside _get_patch_app_updates for changed apps.
press/press/doctype/bench_update/bench_update.py Adds trigger_patch_deploy flag to deploy(); uses plain raise Exception for the suspended-builds case, which drops the user-facing message in the non-new-deploy-flow path and produces a 500.
press/press/doctype/release_group/release_group.py Adds can_run_patch_build, _get_previous_candidate, and _has_active_benches helpers; contains a redundant frappe.db.get_value for the public field that is already in the loaded release_group_doc.
press/press/doctype/release_pipeline/release_pipeline.py Adds initiate_patch_deploy task, propagates trigger_patch_deploy through prepare_deployment, and skips retry logic for patch builds in monitor_build_success; error handling via ReleasePipelineFailure is consistent.
press/press/doctype/deploy_candidate/deploy_candidate.py Adds trigger_patch_deploy whitelisted method; suspension check is present and build guard in run_patch_build catches invalid states.
press/press/doctype/deploy_candidate/docker_output_parsers.py Adds PatchBuildOutputParser for streaming build/push output; parse_push_output early-returns on missing data so no_push builds are safe; UploadStepUpdater.process guards against missing upload step.
press/api/bench.py Threads trigger_patch_deploy flag through both old and new deploy flow paths cleanly.
dashboard/src/components/group/UpdateReleaseGroupDialog.vue Adds Deploy as Patch button and patch-deploy-info info step; patchDeploy resource wires correctly to backend; step navigation and back-button logic are consistent.
press/press/doctype/release_group/test_can_run_patch_build.py Comprehensive test suite for can_run_patch_build covering all guard conditions including dual-platform bench scenarios.

Sequence Diagram

sequenceDiagram
    participant UI as Dashboard (Vue)
    participant API as bench.deploy_and_update
    participant BU as BenchUpdate.deploy()
    participant DC as DeployCandidate.trigger_patch_deploy()
    participant DCB as DeployCandidateBuild.run_patch_build()
    participant Agent as Agent (builder/patch_build)
    participant AJ as AgentJob processor

    UI->>API: "deploy_and_update(trigger_patch_deploy=True)"
    API->>BU: "bench_update.deploy(trigger_patch_deploy=True)"
    BU->>DC: candidate.trigger_patch_deploy()
    DC-->>BU: "{error: False, name: build_name}"
    BU-->>API: build_name

    Note over DCB: after_insert → run_patch_build()
    DCB->>DCB: can_run_patch_build() guard
    DCB->>DCB: _get_previous_candidate()
    DCB->>Agent: run_patch_build(base_image, app_updates, …)
    Agent-->>AJ: job updates (step statuses + output)

    AJ->>DCB: process_run_patch_build()
    DCB->>DCB: _sync_patch_build_step_statuses()
    DCB->>DCB: patch_build_output_parser.parse_and_update()

    alt Build succeeded
        DCB->>DCB: update_deploy_candidate_with_build()
        alt Second platform required
            DCB->>DCB: insert new DeployCandidateBuild (other platform)
        else deploy_after_build
            DCB->>DCB: create_deploy()
        end
    else Build failed
        DCB->>DCB: handle_build_failure()
    end
Loading

Reviews (7): Last reviewed commit: "Merge branch 'develop' into quick-builds" | Re-trigger Greptile

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 24, 2026

Greptile Summary

This PR introduces "patch builds" — a fast deploy path that snapshots an existing bench image, applies only changed app code, and skips a full Docker rebuild. It adds the full stack: agent API call, DCB lifecycle (run_patch_build, step sync, dual-platform chaining), can_run_patch_build eligibility check with 13 test cases, and a "Deploy as Patch" button in the deploy dialog guarded by that check.

  • Backend: new trigger_patch_deploy on DeployCandidate, run_patch_build / send_patch_build_instructions / _process_patch_build_job on DeployCandidateBuild, PatchBuildOutputParser for step-level output, and can_run_patch_build eligibility logic in release_group.py.
  • Pipeline integration: trigger_patch_deploy flag threaded through deploy_and_updateBenchUpdate.deploy → both the old and new (ReleasePipeline) flows, with suspension-error handling fixed for both.
  • Frontend: UpdateReleaseGroupDialog shows a "Deploy as Patch" outline button when can_run_patch_build is true, leading to an info step before submitting the patchDeploy resource.

Confidence Score: 4/5

Safe to merge with awareness of open items from prior review rounds; the suspension-error KeyError and bench_update default-value issues have been addressed in this revision.

Several issues flagged in prior review threads — including the KeyError on previous_releases[app.app], the None base image for arm64 on intel-only history, and the missing default on prepare_deployment's trigger_patch_deploy parameter — remain unresolved in the current diff. The new code added in this revision is otherwise clean.

deploy_candidate_build.py (open KeyError + None base-image issues from prior threads) and release_pipeline.py (missing default on prepare_deployment).

Important Files Changed

Filename Overview
press/press/doctype/deploy_candidate_build/deploy_candidate_build.py Core patch-build logic: adds run_patch_build, send_patch_build_instructions, step sync, and dual-platform chaining. Several edge-cases (KeyError in _get_patch_app_updates, None base image) flagged in earlier review threads remain open; N+1 queries in _get_patch_app_updates are new.
press/press/doctype/release_pipeline/release_pipeline.py Adds initiate_patch_deploy task and wires trigger_patch_deploy through prepare_deployment and create_release. prepare_deployment is missing a default value for trigger_patch_deploy (flagged in previous review thread), which can break in-flight pipelines on deploy.
press/press/doctype/release_group/release_group.py Adds can_run_patch_build, _get_previous_candidate, and _has_active_benches helpers. Logic is well-covered by the companion test file; exposes can_run_patch_build result in deploy_information, adding a few extra DB queries per deploy-dialog load.
press/press/doctype/bench_update/bench_update.py Adds trigger_patch_deploy parameter (defaults to False), routes to trigger_patch_deploy() when set, and raises a clear Exception on suspension errors instead of a bare KeyError.
press/press/doctype/deploy_candidate/deploy_candidate.py Adds trigger_patch_deploy whitelisted method that guards against suspended builds and creates a patch DCB. User-visible message string is not wrapped with _().
press/press/doctype/deploy_candidate/docker_output_parsers.py Adds PatchBuildOutputParser to sync step outputs from agent job steps, and fixes UploadStepUpdater.process to guard against missing last_updated attribute when used from a patch build context.
press/press/doctype/release_group/test_can_run_patch_build.py Good test coverage for all major can_run_patch_build conditions: app add/remove/source-change, dependency change, env var change, public group, archived bench, and dual-platform active-bench scenarios.
dashboard/src/components/group/UpdateReleaseGroupDialog.vue Adds Deploy as Patch button (guarded by can_run_patch_build) that shows an info step before triggering the patch deploy API call. Navigation and state machine look correct.

Sequence Diagram

sequenceDiagram
    participant UI as UpdateReleaseGroupDialog
    participant API as press.api.bench
    participant BU as BenchUpdate
    participant DC as DeployCandidate
    participant DCB as DeployCandidateBuild
    participant Agent as Agent (builder server)

    UI->>API: "deploy_and_update(trigger_patch_deploy=True)"
    API->>BU: "deploy(trigger_patch_deploy=True)"
    BU->>DC: trigger_patch_deploy()
    DC->>DCB: "create_build(patch_build=True).insert()"
    DCB->>DCB: after_insert → run_patch_build()
    DCB->>DCB: can_run_patch_build() guard
    DCB->>DCB: send_patch_build_instructions(previous_candidate)
    DCB->>Agent: run_patch_build(base_image, app_updates, registry, …)
    Agent-->>DCB: job updates (process_run_patch_build)
    DCB->>DCB: _sync_patch_build_step_statuses
    DCB->>DCB: PatchBuildOutputParser.parse_and_update
    alt build succeeded
        DCB->>DCB: update_deploy_candidate_with_build()
        alt second platform required
            DCB->>DCB: _create_platform_patch_build_if_required_and_deploy()
            DCB->>DCB: insert new patch DCB → run_patch_build() (arm64/x86_64)
        else no second platform
            DCB->>DCB: create_deploy()
        end
    else build failed
        DCB->>DCB: handle_build_failure()
    end
Loading

Reviews (12): Last reviewed commit: "Merge branch 'develop' into quick-builds" | Re-trigger Greptile

Comment thread press/press/doctype/deploy_candidate_build/deploy_candidate_build.py Outdated
Comment thread press/press/doctype/deploy_candidate_build/deploy_candidate_build.py Outdated
Comment thread press/press/doctype/bench_update/bench_update.py Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 24, 2026

Codecov Report

❌ Patch coverage is 62.89308% with 118 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.17%. Comparing base (fcc796a) to head (73cf680).
⚠️ Report is 5 commits behind head on develop.

Files with missing lines Patch % Lines
...e/deploy_candidate_build/deploy_candidate_build.py 18.18% 63 Missing ⚠️
.../doctype/deploy_candidate/docker_output_parsers.py 17.07% 34 Missing ⚠️
...press/doctype/release_pipeline/release_pipeline.py 36.84% 12 Missing ⚠️
...press/doctype/deploy_candidate/deploy_candidate.py 28.57% 5 Missing ⚠️
press/agent.py 50.00% 1 Missing ⚠️
press/press/doctype/agent_job/agent_job.py 50.00% 1 Missing ⚠️
press/press/doctype/bench_update/bench_update.py 66.66% 1 Missing ⚠️
press/press/doctype/release_group/release_group.py 97.82% 1 Missing ⚠️

❌ Your patch check has failed because the patch coverage (62.89%) is below the target coverage (75.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##           develop    #6498       +/-   ##
============================================
- Coverage    61.00%   50.17%   -10.84%     
============================================
  Files          110      955      +845     
  Lines        17375    79191    +61816     
  Branches       369      368        -1     
============================================
+ Hits         10600    39733    +29133     
- Misses        6749    39432    +32683     
  Partials        26       26               
Flag Coverage Δ
dashboard 60.97% <ø> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Aradhya-Tripathi
Copy link
Copy Markdown
Contributor Author

@greptileai rereview

@Aradhya-Tripathi
Copy link
Copy Markdown
Contributor Author

@greptileai rereview

@Aradhya-Tripathi
Copy link
Copy Markdown
Contributor Author

Greptile Summary

This PR introduces a "Patch Build" deploy path that bypasses the full Docker image rebuild by pulling only the changed app commits into the existing running container, committing the result, and pushing a new image tag — significantly reducing deploy time for pure app-code updates.

  • Adds can_run_patch_build eligibility gating (same apps/sources/deps/env-vars, active benches required), a trigger_patch_deploy flag threaded through the API → BenchUpdate.deployDeployCandidate.trigger_patch_deploy, and a new \"Run Patch Build\" agent job type with its fixture entry.
  • Adds _process_patch_build_job and PatchBuildOutputParser to handle the shortened step sequence (pull → commit → push) and sync step statuses back from agent job records.
  • Exposes a "Deploy as Patch" button in the dashboard dialog, visible only when the backend signals eligibility via deploy_information.can_run_patch_build.

Confidence Score: 3/5

The new patch-build path has two logic gaps that can cause silent or unexpected failures in production: one in error handling when a patch build fails inside the release pipeline, and one in the multi-arch secondary build flow.

When a patch build fails on the new deploy flow, _check_for_scheduled_build_retries queries for a Run Remote Builder agent job that will never exist for patch builds, throwing DoesNotExistError instead of the expected ReleasePipelineFailure, leaving the pipeline in an ambiguous state. Separately, the secondary-platform build creation updates the previous-candidate pointer before the second build runs its guard, causing it to fail mid-deploy.

release_pipeline.py (_check_for_scheduled_build_retries) and deploy_candidate_build.py (_create_platform_patch_build_if_required_and_deploy) need the most attention before merge.

Important Files Changed

Sequence Diagram

Prompt To Fix All With AI

Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
press/press/doctype/release_pipeline/release_pipeline.py:323-330
**Hardcoded job type breaks patch-build failure handling**

`_check_for_scheduled_build_retries` fetches an agent job filtering by `"job_type": "Run Remote Builder"`, but patch builds create an agent job of type `"Run Patch Build"`. When a patch build fails, `monitor_build_success` calls this method and `frappe.get_doc` raises `DoesNotExistError` because no `"Run Remote Builder"` job exists for the build. This exception is not a `ReleasePipelineFailure`, so it bypasses the `except ReleasePipelineFailure` block in `create_release`, leaving the pipeline in an undefined error state rather than cleanly transitioning to `"Failure"`.

The job type should be determined from the build record itself (or accepted as a parameter) rather than hardcoded to the regular-build value.

### Issue 2 of 3
press/press/doctype/deploy_candidate_build/deploy_candidate_build.py:1123-1142
**Secondary patch build skips the `can_run_patch_build` guard**

`_create_platform_patch_build_if_required_and_deploy` inserts a new `DeployCandidateBuild` with `run_build=0` and calls `new_dcb.run_patch_build()`. `run_patch_build()` re-checks `can_run_patch_build(self.group)`, but by the time the secondary platform build is triggered the first build has already committed image metadata, so the "previous candidate" returned by `_get_previous_candidate` is now the *new* candidate that was just pushed — not the original baseline. This can cause `can_run_patch_build` to return `False` for the secondary build, throwing `"Patch build cannot be run."` mid-deploy and leaving the first platform's image pushed but the second one never created, resulting in a partial multi-arch deploy with no recovery path.

### Issue 3 of 3
dashboard/src/components/group/UpdateReleaseGroupDialog.vue:679-681
**"Deploy as Patch" is reachable on the `restrict-build` step without acknowledgement**

`canShowDeploy` is `!canShowNext`, and `canShowNext` returns `false` on the `restrict-build` step, so the "Deploy as Patch" button is visible there. Clicking it submits with `run_will_fail_check: !this.ignoreWillFailCheck` — which evaluates to `true` when the checkbox hasn't been ticked — causing the backend to re-run the check and immediately return a `BuildValidationError`, then cycling the user back to the same step. The button should either be hidden on the `restrict-build` step, or `deployAsPatch` should mirror the `updateBench` guard that rejects submission when `this.restrictMessage` is set and `!this.ignoreWillFailCheck`.

Reviews (3): Last reviewed commit: "chore(build): Rename as patch build" | Re-trigger Greptile

The second issue is not correct, since the secondary platform build will not create a new deploy candidate doctype instead will associate the deploy_Candidate_build doctype to the older deploy_candidate doctype

@Aradhya-Tripathi
Copy link
Copy Markdown
Contributor Author

@greptileai rereview

Comment thread press/press/doctype/bench_update/bench_update.py
Comment thread press/press/doctype/release_pipeline/release_pipeline.py
Aradhya-Tripathi and others added 3 commits May 25, 2026 21:45
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
@Aradhya-Tripathi
Copy link
Copy Markdown
Contributor Author

@greptileai rereview

Comment on lines +1057 to +1063
def _get_base_image_for_platform(self, previous_candidate: "DeployCandidate") -> str:
build_name = (
previous_candidate.arm_build
if self.platform == "arm64"
else (previous_candidate.intel_build or previous_candidate.arm_build)
)
return frappe.db.get_value("Deploy Candidate Build", build_name, "docker_image")
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.

P1 None base image passed to agent for arm64 patch builds on intel-only history

When a release group previously had only intel servers (so previous_candidate.arm_build is None) and an arm64 server is later added, can_run_patch_build still returns True (it checks _has_active_benches, which only requires the arm bench if the previous candidate already has arm_build set). After the intel patch build succeeds, _create_platform_patch_build_if_required_and_deploy creates an arm64 patch build whose run_patch_build then calls _get_base_image_for_platform("arm64"). With previous_candidate.arm_build is None, build_name is None, frappe.db.get_value returns None, and "base_image": None is sent to the agent — causing the agent request to fail or produce a cryptic error.

The root cause is that can_run_patch_build doesn't verify that the previous candidate has a build for every platform the current candidate requires. Adding a platform-requirements comparison (e.g. checking that previous_candidate.arm_build exists when the current candidate has requires_arm_build=True) would prevent this path from being reachable.


@task(queue=_get_task_execution_queue())
def prepare_deployment(self, apps, sites, run_will_fail_check) -> tuple[str, str]:
def prepare_deployment(self, apps, sites, run_will_fail_check, trigger_patch_deploy) -> tuple[str, str]:
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.

P1 Missing default value for trigger_patch_deploy in prepare_deployment. This task is enqueued by create_release — if an old create_release serialized a prepare_deployment call with 3 positional arguments before this code was deployed, the new code would pick up that task and raise TypeError: missing 1 required positional argument: 'trigger_patch_deploy', silently breaking in-flight pipelines at deployment time. Every other newly-added parameter in this PR (create_release, create_deploy_candidate) correctly defaults to False; this one was missed.

Suggested change
def prepare_deployment(self, apps, sites, run_will_fail_check, trigger_patch_deploy) -> tuple[str, str]:
def prepare_deployment(self, apps, sites, run_will_fail_check, trigger_patch_deploy=False) -> tuple[str, str]:

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Jun 2, 2026

Codecov Report

❌ Patch coverage is 60.98361% with 119 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.14%. Comparing base (af89b0c) to head (2e42c6d).
⚠️ Report is 24 commits behind head on develop.

Files with missing lines Patch % Lines
...e/deploy_candidate_build/deploy_candidate_build.py 18.18% 63 Missing ⚠️
.../doctype/deploy_candidate/docker_output_parsers.py 17.07% 34 Missing ⚠️
...press/doctype/release_pipeline/release_pipeline.py 36.84% 12 Missing ⚠️
...press/doctype/deploy_candidate/deploy_candidate.py 28.57% 5 Missing ⚠️
press/press/doctype/release_group/release_group.py 93.75% 2 Missing ⚠️
press/agent.py 50.00% 1 Missing ⚠️
press/press/doctype/agent_job/agent_job.py 50.00% 1 Missing ⚠️
press/press/doctype/bench_update/bench_update.py 66.66% 1 Missing ⚠️

❌ Your patch status has failed because the patch coverage (60.98%) is below the target coverage (75.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #6498      +/-   ##
===========================================
+ Coverage    49.43%   50.14%   +0.71%     
===========================================
  Files          976      984       +8     
  Lines        81257    82293    +1036     
  Branches       379      522     +143     
===========================================
+ Hits         40166    41268    +1102     
+ Misses       41063    40993      -70     
- Partials        28       32       +4     
Flag Coverage Δ
dashboard 62.76% <ø> (+2.45%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Aradhya-Tripathi Aradhya-Tripathi merged commit dd00289 into develop Jun 2, 2026
9 of 14 checks passed
@Aradhya-Tripathi Aradhya-Tripathi deleted the quick-builds branch June 2, 2026 18:17
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