Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Code ReviewThis is a solid, well-structured addition. The namespaced Bugs / correctness
Rollback only restores the first container Staging deploy fires on every branch push push:
branches:
- main
- master
- ${{ vars.STAGING_APP_BRANCH }}(or simply list the concrete branch names and let users edit after generating, since Security / safety
Code qualityDuplicated Hardcoded No caching in
Minor
Inline comments are attached to the specific lines above. |
Code Review: Add reusable GitHub Actions flow generatorOverall this is a well-structured addition that brings a Heroku-Flow-style CI pipeline to 🔴 Critical — SecurityAnyone can trigger deployments via issue comments (public repos)
The fix is to check if: |
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) &&
github.event.comment.body == '/deploy-review-app') ||
...This is the recommended pattern from GitHub's security lab for slash-command workflows. Without it, a malicious actor can exhaust your staging quota, exfiltrate 🟠 Important — Bugs
The substitution should be scoped to only the files that were just generated. Code duplication: The method is defined identically in both 🟡 Minor / Design
on:
push:
branches: ["**"]This enqueues the Token interpolated directly into composite action shell In TOKEN="${{ inputs.token }}"The token value is inlined into the generated script text. GitHub Actions masks it in logs, but the recommended pattern for secrets passed through composite action inputs is to expose them via env:
TOKEN: ${{ inputs.token }}and reference Hardcoded dependency versions will go stale
Health check rejects healthy apps that return 4xx at The promotion workflow accepts only HTTP 200/301/302 as healthy. Rails apps behind authentication commonly return 401 or 302 at Rollback fires but does not await or verify success The rollback step calls |
Code Review – PR #278: Add reusable GitHub Actions flow generatorOverall this is a solid, well-structured addition. The namespaced Security1. No commenter-permission check on
On a public repository this means any GitHub user can trigger a review-app deploy — consuming staging resources and potentially running release scripts against the staging org. Add an github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'2. Staging token passed as a CLI argument In Arguments to CLI processes appear in Performance3. Staging deploy fires on every branch push
Code Quality4. Duplicate
5. After generation the method walks 6.
GENERATED_FILES = Dir.glob(
Cpflow.root_path.join("lib/github_flow_templates/.github/**/*")
).select { |f| File.file?(f) }
.map { |f| f.delete_prefix(Cpflow.root_path.join("lib/github_flow_templates/").to_s) }
.freezeMinor / Nice-to-have
|
Code Review: Add reusable GitHub Actions flow generatorOverall this is a well-structured PR that brings meaningful CI/CD scaffolding to Bugs / Correctness
Missing concurrency guard on
Code Quality
Release-phase detection is fragile if cpflow config -a "${APP_NAME}" | grep -q "release_script:"; thenThis grep matches on the raw text output of
SecurityGitHub Actions not pinned to commit SHAs
Performance / RobustnessHealth-check window is 2 minutes (12 × 10 s)
Nits
|
Review summary — how feedback was addressedThanks for the detailed reviews. The Fixed in
Fixed in earlier commits (
The unresolved inline threads pointed at themes that have all been addressed. I've replied + resolved the threads corresponding to the 11 items above; remaining stale threads are re-fires of the same feedback on earlier commits. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 5 total unresolved issues (including 3 from previous reviews).
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Rollback guard fires on failed capture with empty state
- I updated both rollback step guards to also require
steps.capture-current.outcome == 'success', preventing rollback execution when capture failed and emitted no output.
- I updated both rollback step guards to also require
- ✅ Fixed: SQLite path copies Postgres templates that are immediately overwritten
- I changed
base_template_filesto returnBASE_TEMPLATE_FILES - SQLITE_TEMPLATE_FILESfor SQLite projects so overlapping files are not copied twice.
- I changed
Or push these changes by commenting:
@cursor push 346f23824c
Preview (346f23824c)
diff --git a/lib/command/generate.rb b/lib/command/generate.rb
--- a/lib/command/generate.rb
+++ b/lib/command/generate.rb
@@ -61,7 +61,9 @@
end
def base_template_files
- sqlite_project? ? BASE_TEMPLATE_FILES : BASE_TEMPLATE_FILES + POSTGRES_TEMPLATE_FILES
+ return BASE_TEMPLATE_FILES - SQLITE_TEMPLATE_FILES if sqlite_project?
+
+ BASE_TEMPLATE_FILES + POSTGRES_TEMPLATE_FILES
end
def template_variables
diff --git a/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml b/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml
--- a/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml
+++ b/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml
@@ -261,7 +261,7 @@
exit 1
- name: Roll back on failure
- if: failure() && steps.capture-current.outputs.rollback_state != '{}'
+ if: failure() && steps.capture-current.outcome == 'success' && steps.capture-current.outputs.rollback_state != '{}'
env:
ROLLBACK_STATE: ${{ steps.capture-current.outputs.rollback_state }}
PRODUCTION_APP_NAME: ${{ vars.PRODUCTION_APP_NAME }}
@@ -296,7 +296,7 @@
fi
- name: Wait for rollback readiness
- if: failure() && steps.capture-current.outputs.rollback_state != '{}'
+ if: failure() && steps.capture-current.outcome == 'success' && steps.capture-current.outputs.rollback_state != '{}'
env:
ROLLBACK_STATE: ${{ steps.capture-current.outputs.rollback_state }}
PRODUCTION_APP_NAME: ${{ vars.PRODUCTION_APP_NAME }}You can send follow-ups to the cloud agent here.
Code ReviewOverviewThis PR adds a substantial GitHub Actions generator ( Specific concerns below, roughly ordered by impact. Security1. Control Plane token exposed in process table (
2. The comment in the code acknowledges that composite-action inputs are not auto-masked. The mask is applied on the first line of the script, so the window is tiny — but Code Quality3. Every 4. Ruby version detection logic is duplicated across three files
5. Hardcoded package manager fallback versions in the generated Dockerfile
Minor Issues6. The method body is just 7. The rescue block returns 8. Health-check tuning constants are buried in the workflow
Test CoverageThe spec suite is comprehensive — particularly the security-boundary test for Gemfile code execution and the YAML validity check over all generated files. A few gaps worth noting:
|
Address Cursor Bugbot review feedback on PR #278. - Promote workflow: tighten rollback guard to also reject empty rollback_state. When the capture-current step fails before writing the heredoc output under `set -euo pipefail`, the output resolves to an empty string, which previously satisfied `!= '{}'` and triggered the rollback step on zero state. The loop iterated zero times so production was unharmed, but the guard's intent was violated. Require both non-empty and non-'{}'. - Generator: subtract SQLITE_TEMPLATE_FILES from the base copy list when the project is SQLite-backed, so controlplane.yml, release_script.sh, templates/app.yml, and templates/rails.yml aren't copied twice (first from generator_templates/, then immediately overwritten with force:true from generator_templates_sqlite/). Eliminates confusing force-overwrite messages and duplicate entries in generated_paths. - Update spec assertion to match the new rollback guard expression.
Review addressed (commit
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 6 total unresolved issues (including 5 from previous reviews).
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: SQLite template missing
maintenance_workloadconfig key- Added
maintenance_workload: maintenanceto the SQLitecommonalias so generated SQLite apps have maintenance mode workload configured like the PostgreSQL template.
- Added
Or push these changes by commenting:
@cursor push acef5afc22
Preview (acef5afc22)
diff --git a/lib/generator_templates_sqlite/controlplane.yml b/lib/generator_templates_sqlite/controlplane.yml
--- a/lib/generator_templates_sqlite/controlplane.yml
+++ b/lib/generator_templates_sqlite/controlplane.yml
@@ -21,6 +21,7 @@
- rails
additional_workloads: []
+ maintenance_workload: maintenance
release_script: release_script.sh
stale_app_image_deployed_days: 5You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 46c4871. Configure here.
Code Review: PR #278 — Add reusable GitHub Actions flow generatorOverall assessment: This is a large, well-thought-out feature that adds significant value. The architecture is clean, the documentation is thorough, and the security-conscious design choices (TOCTOU-aware PR SHA resolution, author_association gating, SSH key masking, token-via-env-var) are commendable. Several issues are worth addressing before merging. Security
The deploy workflow uses One remaining concern: Pinned The composite action uses Correctness / Bugs
In
Ruby 3.0.x reached end-of-life in March 2024. A repo pinned to Ruby 3.0.7 would pass the readiness check with
Cancelling an in-flight Staging workflow fetches full git history without explanation The PerformanceSequential HTTP calls for gem/npm version checks
Code QualityDuplicated Ruby version inference logic
Fragile error-pattern matching in The script infers "app not found" vs "real error" by matching substrings of Minor
|
| return false if version_flag?(argv) | ||
|
|
||
| command_class = command_class_for_argv(argv) | ||
| command_class ? command_class::REQUIRES_STARTUP_CHECKS : false |
There was a problem hiding this comment.
When command_class_for_argv returns nil (unrecognized command name), this returns false, silently skipping startup checks before Thor reports "unknown command". Before this PR, startup checks always ran. Consider defaulting to true for unknown commands so a mistyped command still gets the version check:
| command_class ? command_class::REQUIRES_STARTUP_CHECKS : false | |
| command_class ? command_class::REQUIRES_STARTUP_CHECKS : true |
| keyword_init: true | ||
| ) | ||
|
|
||
| LEGACY_RUBY_VERSION = Gem::Version.new("3.0.0") |
There was a problem hiding this comment.
Ruby 3.0.x reached end-of-life in March 2024. A repo on Ruby 3.0.7 passes this check with :pass even though the release line is no longer supported. 3.1.0 is the oldest currently-supported line:
| LEGACY_RUBY_VERSION = Gem::Version.new("3.0.0") | |
| LEGACY_RUBY_VERSION = Gem::Version.new("3.1.0") |
|
|
||
| concurrency: | ||
| group: cpflow-review-app-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }} | ||
| cancel-in-progress: true |
There was a problem hiding this comment.
Cancelling an in-flight cpflow deploy-image run mid-execution can leave the review app in a partially-deployed state (workload update in progress, rollout not yet settled). The delete workflow correctly uses cancel-in-progress: false for this reason. Consider the same here, or at minimum document the risk — a cancelled deploy is harder to diagnose than a failed one.
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 |
There was a problem hiding this comment.
fetch-depth: 0 fetches the full git history. On large repos this meaningfully slows CI. If cpflow build-image only needs the current tree (not commit history), fetch-depth: 1 is sufficient. If there's a reason the full history is required (e.g., version tagging from git describe), a brief comment here would clarify the intent.
| corepack prepare "$package_manager" --activate && \ | ||
| (corepack yarn install --immutable || corepack yarn install --frozen-lockfile); \ | ||
| else \ | ||
| npm install -g yarn@1.22.22 && \ |
There was a problem hiding this comment.
This hardcodes a specific Yarn Classic version. Yarn Classic is in maintenance mode (Yarn ≥ 4 is the actively-developed line), and the version will drift from what projects actually use. Since the Dockerfile already handles projects with a packageManager field via Corepack, the Yarn Classic fallback only applies to legacy projects. Consider making the version a build arg so users can override it without editing the generated file:
| npm install -g yarn@1.22.22 && \ | |
| npm install -g "yarn@${YARN_CLASSIC_VERSION:-1.22.22}" && \ |
Or document it as a known limitation in the template comment.
|
|
||
| def partition_dependencies(dependencies, availability_proc) | ||
| dependencies.each_with_object(unavailable: [], unknown: []) do |dependency, grouped_dependencies| | ||
| case availability_proc.call(dependency) |
There was a problem hiding this comment.
These registry checks are sequential — one HTTP round-trip per gem/package. For a Gemfile with many exact-pinned public gems, this can take minutes. Consider parallelising the lookups with threads, or capping the number checked (e.g., first 20 exact pins) and noting in the output that the rest were skipped.
…stency Covers two must-fix correctness/security items and five optional improvements surfaced by the Claude and Cursor review bots. Must-fix - cpflow-setup-environment action: stop passing the Control Plane token via `--token`, which would expose it in /proc/<pid>/cmdline and ps output. Renamed the step env from TOKEN to CPLN_TOKEN so cpln picks the token up from the environment automatically, matching the pattern already used across the rest of the generated workflows. - generate_github_actions.rb: add an explicit `TEMPLATE_ROOT.directory?` guard before the Dir.glob that builds GENERATED_FILES. A broken gem install previously yielded an empty list and made `cpflow generate-github-actions` silently succeed with zero files; it now fails loudly at load time. Optional - cpflow-build-docker-image action: move SSH key handling into a dedicated "Prepare SSH agent" step that writes the key to ~/.ssh/cpflow_build_key via inline template interpolation, so DOCKER_BUILD_SSH_KEY is never present in the main build step's environment (where ACTIONS_STEP_DEBUG would otherwise dump it). The main build step now consumes the key file and cleans up on exit. - github_flow_readiness_service.rb: document the side-effect memoization in `package_json_parse_error?` so future readers don't assume the ivar is set independently of `parsed_package_json`. - github_flow_readiness_service.rb: surface malformed Gemfile.lock parse errors under CPFLOW_DEBUG so the silent rescue no longer hides diagnostic signal; default behavior (skip gem readiness check on parse error) is unchanged. - generator_templates/Dockerfile: add a comment near the package-manager fallbacks noting that yarn@1.22.22 and pnpm@9.12.3 will go stale and that pinning `packageManager` in package.json avoids them entirely. - generator_templates_sqlite/controlplane.yml: add `maintenance_workload: maintenance` to the common alias for parity with the Postgres template.
Review addressed (commit
|
Code ReviewThis is a substantial, well-structured addition that brings a first-class Heroku Flow experience to Control Plane. The overall design is solid — the three-step bootstrap model is intuitive, the generated workflows handle the important edge cases (fork PR isolation, permission gating, rollback on failure), and the readiness service's decision to parse SecuritySSH key heredoc uses a fixed delimiter ( In
In Code QualityRuby version detection is duplicated
The Rails scaffold check verifies PerformanceGem and npm availability checks run sequentially
Minor
|
| cat > ~/.ssh/cpflow_build_key <<'CPFLOW_SSH_KEY_EOF' | ||
| ${{ inputs.docker_build_ssh_key }} | ||
| CPFLOW_SSH_KEY_EOF |
There was a problem hiding this comment.
The heredoc uses a fixed terminator CPFLOW_SSH_KEY_EOF. If the SSH key value ever contains a line that is exactly that string, the heredoc closes early and the key file is silently truncated — the build would proceed with a malformed key.
The rollback-state block in the promotion workflow already guards against this with a randomized delimiter:
delim="EOF_$(openssl rand -hex 8)"Apply the same pattern here:
| cat > ~/.ssh/cpflow_build_key <<'CPFLOW_SSH_KEY_EOF' | |
| ${{ inputs.docker_build_ssh_key }} | |
| CPFLOW_SSH_KEY_EOF | |
| key_delim="CPFLOW_SSH_KEY_EOF_$(openssl rand -hex 8)" | |
| eval "cat > ~/.ssh/cpflow_build_key <<\"${key_delim}\" | |
| ${{ inputs.docker_build_ssh_key }} | |
| ${key_delim}" | |
| CPFLOW_SSH_KEY_EOF |
Note: Because
${{ inputs.docker_build_ssh_key }}is expanded by GitHub Actions before bash runs, the key value is already a literal string at execution time. Using aneval-based heredoc with a randomized delimiter gives the same masking guarantee while eliminating the fixed-terminator risk.
| env: | ||
| APP_NAME: ${{ vars.REVIEW_APP_PREFIX }}-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }} | ||
| CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }} | ||
| CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} |
There was a problem hiding this comment.
Setting CPLN_TOKEN at the workflow-level env block makes the staging token available to every step in every job — including any third-party actions that may be added later. cpflow and cpln both read CPLN_TOKEN from the environment, so the same behavior is achieved by scoping it to the cpflow-setup-environment composite action input, which already accepts token:.
| CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} | |
| CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }} |
Then pass the secret only where it's consumed:
- name: Setup environment
uses: ./.github/actions/cpflow-setup-environment
with:
token: ${{ secrets.CPLN_TOKEN_STAGING }}
org: ${{ vars.CPLN_ORG_STAGING }}The same applies to cpflow-delete-review-app.yml.
| def ruby_version_from_ruby_version_file | ||
| file_path = root_path.join(".ruby-version") | ||
| return unless file_path.file? | ||
|
|
||
| parse_ruby_version(file_path.read) | ||
| end | ||
|
|
||
| def ruby_version_from_tool_versions | ||
| file_path = root_path.join(".tool-versions") | ||
| return unless file_path.file? | ||
|
|
||
| ruby_line = file_path.readlines(chomp: true).find { |line| line.match?(/^\s*ruby\s+/) } | ||
| return unless ruby_line | ||
|
|
||
| parse_ruby_version(ruby_line.sub(/^\s*ruby\s+/, "")) | ||
| end | ||
|
|
||
| def ruby_version_from_gemfile | ||
| file_path = root_path.join("Gemfile") | ||
| return unless file_path.file? | ||
|
|
||
| ruby_line = file_path.readlines(chomp: true).find { |line| line.match?(/^\s*ruby\s+/) } | ||
| return unless ruby_line | ||
|
|
||
| parse_ruby_version(ruby_line.sub(/^\s*ruby\s+/, "")) | ||
| end | ||
|
|
||
| def parse_ruby_version(source) | ||
| version = RepoIntrospection.parse_ruby_version_string(source) | ||
| return unless version | ||
|
|
||
| Gem::Version.new(version) | ||
| end |
There was a problem hiding this comment.
These three methods (ruby_version_from_ruby_version_file, ruby_version_from_tool_versions, ruby_version_from_gemfile) are nearly identical to the private methods of the same name in Command::Generator. Both classes already delegate parse_ruby_version_string and sqlite_database_in_production? to RepoIntrospection. Moving the detection trio there too — returning a raw version string — would make a single place to update when .tool-versions format or Gemfile syntax changes.
Suggested shape in RepoIntrospection:
def self.inferred_ruby_version_string(root)
ruby_version_from_ruby_version_file(root) ||
ruby_version_from_tool_versions(root) ||
ruby_version_from_gemfile(root)
endThe service can then wrap the result in Gem::Version.new(...) and Generator can use it directly.
| def shakapacker_precompile_hook | ||
| return unless File.file?("config/shakapacker.yml") | ||
|
|
||
| config = File.read("config/shakapacker.yml") | ||
| match = config.match(/^\s*precompile_hook:\s*["']?(.+?)["']?\s*$/) | ||
| match && match[1] |
There was a problem hiding this comment.
The regex ^\s*precompile_hook: will match commented-out YAML lines (e.g. # precompile_hook: old_value), and the lazy quantifier (.+?) can misread folded/quoted multiline values. Since shakapacker.yml is a plain YAML file, parsing it properly costs very little:
| def shakapacker_precompile_hook | |
| return unless File.file?("config/shakapacker.yml") | |
| config = File.read("config/shakapacker.yml") | |
| match = config.match(/^\s*precompile_hook:\s*["']?(.+?)["']?\s*$/) | |
| match && match[1] | |
| def shakapacker_precompile_hook | |
| return unless File.file?("config/shakapacker.yml") | |
| config = YAML.safe_load(File.read("config/shakapacker.yml")) | |
| hook = config.is_a?(Hash) && config["precompile_hook"] | |
| hook.is_a?(String) && !hook.empty? ? hook : nil | |
| rescue Psych::SyntaxError | |
| nil | |
| end |
YAML.safe_load is already required in the file via repo_introspection and is safe here because the file is the developer's own project config.
| # Defaults give a ~6 minute window, which covers most Rails cold boots (asset precompile | ||
| # + db:migrate + workload readiness). | ||
| HEALTH_CHECK_RETRIES: 24 | ||
| HEALTH_CHECK_INTERVAL: 15 |
There was a problem hiding this comment.
The "~6 minute window" comment only accounts for sleep time (24 × 15 s = 360 s). Each iteration also runs curl --max-time 10, so the worst-case wall time is 24 × (15 + 10) = 600 s (~10 minutes). The comment should reflect the actual ceiling, or the curl timeout should be factored into the retry count calculation.
| # Defaults give a ~6 minute window, which covers most Rails cold boots (asset precompile | |
| # + db:migrate + workload readiness). | |
| HEALTH_CHECK_RETRIES: 24 | |
| HEALTH_CHECK_INTERVAL: 15 | |
| # Defaults give a ~10 minute window (retries × (interval + curl max-time)), which | |
| # covers most Rails cold boots (asset precompile + db:migrate + workload readiness). | |
| HEALTH_CHECK_RETRIES: 24 | |
| HEALTH_CHECK_INTERVAL: 15 |
| def http_get(uri) | ||
| http = Net::HTTP.new(uri.host, uri.port) | ||
| http.use_ssl = true | ||
| http.open_timeout = 5 | ||
| http.read_timeout = 5 | ||
| http.get(uri.request_uri) | ||
| rescue StandardError => e |
There was a problem hiding this comment.
Gem and npm version checks run sequentially with up to 5 s per request. For a project with many exact-pinned direct dependencies the readiness command can take well over a minute before returning. The checks are fully independent and the results are cached per name, so they're a natural fit for parallel execution. concurrent-ruby ships as a transitive dependency (via Thor), but plain Thread also works:
def fetch_all_versions_in_parallel(packages, fetch_proc)
packages.map { |pkg| Thread.new { fetch_proc.call(pkg) } }.each(&:join)
endAlternatively, a note in the LONG_DESCRIPTION that the check makes outbound HTTP calls would set expectations without requiring a refactor now.
| LEGACY_RUBY_VERSION = Gem::Version.new("3.0.0") | ||
| LEGACY_BUNDLER_VERSION = Gem::Version.new("2.0.0") | ||
| PUBLIC_RUBYGEMS_REMOTE = "https://rubygems.org" | ||
| REQUIRED_RAILS_PATHS = ["Gemfile", "config/application.rb", "config.ru"].freeze |
There was a problem hiding this comment.
Gemfile, config/application.rb, and config.ru are present in many non-Rails Ruby apps (Rack apps, generator snapshots, tutorial repos). Adding bin/rails to the set would catch the common case of a repo that has the config directory but hasn't run rails new to completion:
| REQUIRED_RAILS_PATHS = ["Gemfile", "config/application.rb", "config.ru"].freeze | |
| REQUIRED_RAILS_PATHS = ["Gemfile", "bin/rails", "config/application.rb", "config.ru"].freeze |
This is a tighter gate without being overly restrictive — every rails new output includes bin/rails.
| name: Show Review App Commands on PR Open | ||
|
|
||
| on: | ||
| pull_request_target: |
There was a problem hiding this comment.
pull_request_target is the right trigger here — it gives write permission to post a comment even when the PR is from a fork. This is safe because no PR code is checked out: the job only calls actions/github-script with a hardcoded message. Future maintainers may be tempted to switch this to pull_request (or add a checkout step); a short comment prevents that regression:
| pull_request_target: | |
| pull_request_target: | |
| # pull_request_target is intentional: it has write permission to comment on fork PRs. | |
| # No PR code is checked out here — keep it that way to preserve this safety guarantee. |


Summary
cpflow generate-github-actionswith reusable review-app, staging, promotion, and cleanup workflowsVerification
Note
Medium Risk
Introduces new CI workflow templates and changes generator output/CLI startup-check behavior, which can affect how users bootstrap and deploy projects. Core infrastructure interactions remain largely unchanged, but the breadth of scaffolding and new automation warrants careful review.
Overview
Adds a new reusable GitHub Actions pipeline scaffold via
cpflow generate-github-actions, including composite actions and workflows for opt-in PR review apps (comment-driven), staging auto-deploys, manual staging→production promotion with health-check + rollback, and nightly stale review-app cleanup.Introduces local-only helper commands for rollout (
cpflow github-flow-readiness+cpflow ai-github-flow-prompt) and updates docs to emphasize a 3-step bootstrap flow and required GitHub secrets/vars, including optional SSH/extra docker build args for private dependencies.Upgrades
cpflow generatescaffolding: infers app prefix and Ruby version, injects detected frontend precompile hooks, generates a Node-capable Dockerfile with package-manager auto-detection, addsrelease_script.sh, and switches templates to persistent SQLitedb/storagevolumes whenconfig/database.ymlindicates SQLite. Also tweaks CLI startup behavior so commands can opt out ofcpln/version checks, and allowscopy-image-from-upstreamto take its token fromCPLN_UPSTREAM_TOKEN.Adds extensive RSpec coverage for the new generators/readiness service and adjusts test harness/
latest-imagespec to be more resilient.Reviewed by Cursor Bugbot for commit 2daf69b. Bugbot is set up for automated code reviews on this repo. Configure here.