Skip to content

Fix scheduled slow-suite regressions - #413

Open
justin808 wants to merge 6 commits into
mainfrom
codex/409-scheduled-slow-suite
Open

Fix scheduled slow-suite regressions#413
justin808 wants to merge 6 commits into
mainfrom
codex/409-scheduled-slow-suite

Conversation

@justin808

@justin808 justin808 commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

  • stop only workloads that are both configured for a stale app and still live, avoiding failures on removed workloads while preserving required config validation
  • contain upstream authorization failures inside the progress step so the temporary profile is always cleaned up
  • retry transient workload image-update failures for a bounded window and report endpoints only after a successful update
  • make the slow build assertion compare parsed shell tokens instead of raw escaping

Fixes #409

Diagnosis

The scheduled slow suite exposed four regressions addressed by this PR: a stale raw-string matcher after shell escaping changed, an unscoped stale-app stop against a configured-but-absent workload, an API-specific authorization exception escaping the command step, and an eventually-consistent image update being treated as permanently failed. The fix is intentionally narrow to those seams.

The latest scheduled run on current main, 29631251499, reproduced all four issue-linked failures. Its other three failures were distinct live-service flakes: a reset post-create hook connection, a reused runner command returning status 64, and a successful image run missing expected log output.

Validation

  • rebased candidate: 261d849efbdcc6d120a93c268f8d3bdb24f79fde on main e73d8483029183a3c85a176187d9ad103c23bb52
  • focused regression/unit set — 52 examples, 0 failures
  • .agents/bin/lint — 209 files, 0 offenses
  • .agents/bin/docs — passed
  • .agents/bin/validate — attempted; local integration setup stops before patched behavior because CPLN_ORG is unavailable
  • codex review --base origin/main at the candidate head — no actionable regressions
  • previous exact-head Fast replay 29492232103, attempt 2 — passed after the external GVC quota condition cleared
  • current-head PR gates — running
  • current-head manual Fast + Slow replay — running
  • independent checker — running against the immutable candidate head

Decision log

  • Retry only the Control Plane workload image update, not endpoint resolution or the surrounding deploy sequence.
  • Bound retries to 30 attempts with a one-second interval; deterministic failures may take the same bounded window because the command seam exposes only a boolean result.
  • Preserve the destructive-operation boundary: no direct live cleanup or deployment was run by the maker, coordinator, or checker.
  • Preserve the required-key contract for both app_workloads and additional_workloads; making either optional is outside issue Scheduled slow suite failing #409.
  • Merge ledger: not applicable per the repository seam.
  • Benchmarks: not applicable; this is not a performance-sensitive change and the repository defines no benchmark seam.

QA Evidence

Status: exact-current-head hosted and independent gates in progress.

Summary by CodeRabbit

  • Bug Fixes

    • Improved stopping of stale applications by targeting only configured, active workloads.
    • Added clear validation for incomplete application workload configuration.
    • Improved handling of invalid upstream credentials with clearer failure messaging.
    • Added bounded retries for transient workload image deployment failures.
    • Prevented deployment summaries from reporting workloads whose image updates did not succeed.
  • Documentation

    • Added unreleased changelog notes covering scheduled slow-suite regression fixes.

@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes several scheduled slow-suite regressions. The main changes are:

  • Stops only configured workloads that still exist in stale apps.
  • Handles upstream authorization failures inside the progress step.
  • Retries workload image updates before reporting endpoints.
  • Compares parsed shell tokens in the build-image test.

Confidence Score: 4/5

Ambiguous stale-app config matches can leave live workloads running.

  • The new stop path selects one matching config before intersecting workload names.
  • Overlapping prefix matches can select an incomplete workload list and silently skip required stops.
  • The retry and upstream-profile cleanup changes are consistent with their callers.

lib/command/cleanup_stale_apps.rb

Important Files Changed

Filename Overview
lib/command/cleanup_stale_apps.rb Adds live-workload filtering, but ambiguous prefix-based config matches can leave workloads running.
lib/command/copy_image_from_upstream.rb Converts upstream forbidden responses into normal failed-step handling while preserving profile cleanup.
lib/command/deploy_image.rb Adds bounded retries and records endpoints only after a successful image update.
lib/core/controlplane.rb Returns the workload-update command result so the deploy command can retry failures.
spec/command/build_image_spec.rb Makes the build argument assertion independent of raw shell escaping.

Reviews (1): Last reviewed commit: "Fix scheduled slow-suite regressions" | Re-trigger Greptile

Comment thread lib/command/cleanup_stale_apps.rb Outdated
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@justin808, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d55cf299-71eb-4069-8302-348e7ef17d94

📥 Commits

Reviewing files that changed from the base of the PR and between f1ae55a and 7cb1f8a.

📒 Files selected for processing (1)
  • spec/command/ps_stop_spec.rb

Walkthrough

The changes update stale-app stop-mode workload selection, handle forbidden upstream image requests, retry workload image updates, and add regression coverage for these behaviors and related slow-suite test execution.

Changes

Reliability fixes

Layer / File(s) Summary
Configured live workload cleanup
lib/command/cleanup_stale_apps.rb, spec/command/cleanup_stale_apps_spec.rb, CHANGELOG.md
Stop mode now stops only configured workloads that are live, validates required configuration, and documents the fix.
Forbidden upstream image handling
lib/command/copy_image_from_upstream.rb, spec/command/copy_image_from_upstream_unit_spec.rb
Forbidden upstream requests return failure status, write the error to temporary stderr, and preserve cleanup behavior.
Retryable workload image updates
lib/command/deploy_image.rb, lib/core/controlplane.rb, spec/command/deploy_image_unit_spec.rb
Workload image updates return status, retry up to the configured limit, and avoid reporting unsuccessful deployments.
Slow-suite command test adjustments
spec/command/build_image_spec.rb, spec/command/run_spec.rb
Build command assertions use tokenized arguments, and the token-based run test waits before log attachment.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CleanupStaleApps
  participant Config
  participant Controlplane
  participant Cpflow
  CleanupStaleApps->>Config: Read configured workloads
  CleanupStaleApps->>Controlplane: Fetch live workloads
  CleanupStaleApps->>Cpflow: Stop matching workloads
Loading
sequenceDiagram
  participant DeployImage
  participant Controlplane
  participant Shell
  DeployImage->>Controlplane: Update workload image reference
  Controlplane->>Shell: Execute update command
  Shell-->>Controlplane: Return success or failure
  DeployImage->>Controlplane: Retry failed update
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: fixing scheduled slow-suite regressions.
Linked Issues check ✅ Passed The PR addresses #409 by fixing the slow-suite regressions in workload stopping, upstream auth handling, image retries, and build assertions.
Out of Scope Changes check ✅ Passed The changes stay focused on the slow-suite regressions and related test/documentation updates, with no clear unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/409-scheduled-slow-suite

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.

@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review: Fix scheduled slow-suite regressions (PR 413)

Overview: this PR addresses four independent regressions surfaced by the scheduled slow-suite run — (1) cleanup-stale-apps --mode=stop now only stops workloads that are both configured and still live, (2) copy-image-from-upstream catches ForbiddenError inside the progress step so the temp profile is cleaned up via a normal "failed!" step instead of an unhandled exception, (3) deploy-image retries transient workload image-update failures for up to 30s before giving up, (4) the slow build-image spec matches parsed shell tokens instead of a raw regex. The fix is narrowly scoped to the diagnosed seams and is backed by new/updated unit specs (cleanup_stale_apps_spec.rb, copy_image_from_upstream_unit_spec.rb, deploy_image_unit_spec.rb, controlplane_spec.rb).

Code quality

  • stop_configured_live_workloads (lib/command/cleanup_stale_apps.rb) is a clean, well-scoped fix: fetching live workloads and intersecting with configured ones (app_workloads + additional_workloads) avoids the previous unscoped ps:stop -a app call failing on workloads that no longer exist. Good test coverage of the intersection logic.
  • The copy_image_from_upstream.rb fix is narrowly targeted at the specific ForbiddenError case reported by the diagnosis, consistent with the existing broad-rescue pattern already used in maintenance_mode.rb for similar eventually-consistent polling.
  • build_image_spec.rb's switch from a raw regex match to Shellwords.split + token-pair comparison is a solid fix for shell-escaping brittleness.

Potential issues

  1. deploy_image.rb: the 30s retry now applies uniformly to all image-update failures, not just transient ones. cp.workload_set_image_ref switched from perform! (immediate Shell.abort on any failure) to perform wrapped in a retry_on_failure step. Since perform cannot distinguish a transient failure from a permanent one (bad workload name, invalid image ref, auth error, etc.), every failure now takes up to ~30 seconds (30 attempts times 1s) to surface instead of failing fast. This is a UX/perf regression for the common case of a deterministic misconfiguration, and arguably in tension with the decision log ("Bound retries... without hiding permanent failures") — it does not hide them, but it does delay reporting them by up to 30s every time. Worth considering whether permanent failures can be classified and fast-failed. See inline comment.

  2. deploy_image.rb: the retry boundary also covers endpoint_for_workload, not just the image update call, which is inconsistent with the stated decision to "retry only the Control Plane workload image update, not the surrounding deploy sequence." Because both cp.workload_set_image_ref and deployed_endpoints[workload] = endpoint_for_workload(workload_data) run inside the same retried block, a transient failure to resolve/propagate the endpoint (independent of whether the image update itself already succeeded) will cause the block to return falsy and re-issue the cpln workload update command again on the next attempt — a redundant (if idempotent) re-PATCH triggered by an unrelated read-side hiccup. Consider moving the endpoint lookup out of the retried block once updated is true. See inline comment.

  3. Minor: stop_configured_live_workloads raises a bare RuntimeError ("Cannot find config for stale app...") outside of a step block, so it propagates as an unhandled-looking exception rather than a formatted "failed!" step — though this matches existing patterns elsewhere in the codebase (e.g. ensure_upstream_org!), so it is consistent, just noting for awareness.

Security
No new security concerns. The ForbiddenError message rendering was verified via the new unit spec to not leak the upstream token (expect(progress.string).not_to include("invalid-token")), a good, deliberate check.

Tests
Coverage looks solid and matches the diagnosed seams — retry-then-succeed and retry-exhausted-then-fail paths are both tested for deploy-image, and the stale-app workload intersection is tested for both configured/live and configured/missing/unconfigured-but-live cases.

Comment thread lib/command/deploy_image.rb Outdated
Comment thread lib/command/deploy_image.rb
Comment thread lib/command/cleanup_stale_apps.rb Outdated
Comment thread lib/command/deploy_image.rb
Comment thread spec/command/build_image_spec.rb
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review: Fix scheduled slow-suite regressions (#413)

Overview

This PR fixes four independent regressions surfaced by the scheduled slow-suite run (issue #409):

  1. cleanup-stale-apps --mode=stop now stops only the intersection of configured workloads (app_workloads + additional_workloads) and live workloads fetched from the GVC, instead of blindly delegating to ps:stop -a app (which fails if a configured workload no longer exists).
  2. copy-image-from-upstream now rescues ControlplaneApiDirect::ForbiddenError inside the fetch_upstream_image_url step, converting an unhandled exception into a clean failed-step message before the ensure block cleans up the temporary profile.
  3. deploy-image retries workload_set_image_ref up to 30 times (1s apart) via with_retry, and only records a deployed endpoint after a successful update. This required switching Controlplane#workload_set_image_ref from perform! (hard abort) to perform (returns a boolean) so failures are retryable rather than fatal.
  4. The slow build-image spec now matches on parsed Shellwords tokens instead of a raw regex, avoiding false negatives from shell-escaping changes.

Code quality

  • Changes are narrowly scoped to the actual regression seams, consistent with the PR's stated intent — no unrelated refactors.
  • Good use of existing helpers (with_retry, step) rather than reinventing retry/error-reporting logic.
  • New unit specs (copy_image_from_upstream_unit_spec.rb, additions to deploy_image_unit_spec.rb, cleanup_stale_apps_spec.rb, controlplane_spec.rb) directly target each fixed seam and assert both the happy path and the bounded-failure path (e.g. exhausting all 30 retry attempts).
  • stop_configured_live_workloads correctly reuses Config#find_app_config's existing prefix-matching (match_if_app_name_starts_with) semantics, so dynamically-named stale apps still resolve their config the same way stale_apps discovery does.

Potential issues (posted inline)

  • cleanup_stale_apps.rb: Array(app_config[:app_workloads]) treats a missing config key as [] rather than raising, unlike the previous delegation through Config#[] which raised a clear "Can't find option" error. A stale app missing app_workloads in controlplane.yml would now silently stop nothing instead of failing loudly. Likely a rare edge case in practice, but worth confirming it's intentional.
  • deploy_image.rb: The step block's last statement is deployed_endpoints[workload] = endpoint_for_workload(workload_data), whose value becomes the step's success/failure signal. If endpoint_for_workload returns nil (DNS unresolvable and the deployments API hasn't caught up yet), the step is reported as failed and the whole deploy aborts — even though the actual image update already succeeded. This is pre-existing behavior (not introduced by this PR), but since this PR is specifically about tolerating eventual consistency in this exact code path, it may be worth extending the same tolerance to endpoint resolution.
  • build_image_spec.rb: relies on Shellwords being transitively loaded rather than requiring it explicitly in the spec file (minor hygiene nit; controlplane_spec.rb does require it explicitly).

Security

  • No secrets are leaked in the new error paths: ForbiddenError#message is built only from the URL and HTTP response, and the new spec explicitly asserts the upstream token never appears in progress output.
  • The perform!perform change for workload_set_image_ref is scoped to its single call site (deploy_image.rb), so the loss of the automatic Shell.abort doesn't silently affect other commands.

Performance

  • stop_configured_live_workloads now issues one run_cpflow_command("ps:stop", ...) per matched workload rather than a single ps:stop -a app call, which is less efficient (separate Thor/command dispatch per workload) but necessary to skip workloads that no longer exist; acceptable trade-off given cleanup-stale-apps is not a hot path.
  • The bounded retry loop in with_retry sleeps once more than strictly necessary after the final failed attempt (pre-existing behavior of the shared helper, not new to this PR) — worst case adds ~1s to the ~30s failure window.

Test coverage

Solid — each of the four fixes has a matching unit spec that exercises both success and bounded-failure paths, and the PR body documents the validation that was run (49 examples passing, lint/docs clean). The main gap is that end-to-end (real cpln) coverage is explicitly deferred to the hosted CI slow-suite run per the PR description, which is a reasonable trade-off given the local CPLN_ORG limitation noted.

Overall this is a well-scoped, well-tested bug-fix PR. The inline notes above are minor/edge-case observations rather than blockers.

Comment thread lib/command/cleanup_stale_apps.rb
Comment thread lib/command/cleanup_stale_apps.rb
Comment thread lib/command/deploy_image.rb
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review

Focused, well-scoped fix for the four regressions found by the scheduled slow-suite run (issue #409). Each fix is narrowly targeted and backed by a new/updated unit spec that pins the exact failure mode it addresses.

Overview

  1. cleanup-stale-apps --mode=stop (lib/command/cleanup_stale_apps.rb) — now stops only workloads that are both configured (app_workloads + additional_workloads) and still live in the GVC, instead of a blanket ps:stop -a app. Missing config keys now raise a clear error instead of an opaque crash.
  2. copy-image-from-upstream (lib/command/copy_image_from_upstream.rb) — fetch_upstream_image_url now rescues ControlplaneApiDirect::ForbiddenError (a StandardError, not caught by step's existing rescue RuntimeError) so the temporary upstream profile is still cleaned up via the ensure block instead of leaking.
  3. deploy-image (lib/command/deploy_image.rb, lib/core/controlplane.rb) — workload_set_image_ref now returns a boolean (perform instead of perform!) so the caller can retry transient failures up to 30 attempts / 1s apart before giving up, and only records the deployed endpoint after a confirmed successful update.
  4. Test hardening (spec/command/build_image_spec.rb) — the slow build-arg assertion now parses the spawned command with Shellwords.split and checks token adjacency instead of matching a raw, escaping-sensitive regex.

Strengths

  • Each fix maps 1:1 to a diagnosed regression, with no unrelated scope creep.
  • The ForbiddenError rescue is verified to not leak the upstream token into progress/stderr output (spec/command/copy_image_from_upstream_unit_spec.rb), which is a nice security-conscious touch.
  • New specs cover both the happy path and the boundary cases (missing config keys, exhausted retries, endpoint-resolution failure after a successful image update).
  • required_app_option's error message replaces what would otherwise be a raw NoMethodError inside ps:stop for unmatched stale apps — better operator experience.

Minor suggestions (left as inline comments)

  • required_app_option guards against a missing config key but not an explicit nil value (e.g. app_workloads: with no items), which would still hit a raw NoMethodError on the + — slightly undercuts the goal of a friendly error message.
  • --mode=stop now silently skips live-but-unconfigured workloads (a behavior change from stopping all live workloads) — documented in LONG_DESCRIPTION, but a progress.puts noting what was skipped could help operators notice unexpected drift between the GVC and controlplane.yml.
  • Noted (non-blocking) that with_retry treats a signal-killed cpln process (nil from perform) the same as a normal failure — consistent with existing perform/perform! semantics elsewhere, so likely fine as-is.

Risk/Correctness

No security concerns beyond the token-leak check above, which is already covered. The perform!perform change in workload_set_image_ref has a single call site and it's updated correctly to handle the new falsy-return contract. Retry bound (30 attempts × 1s) is reasonable and documented in the decision log.

Nothing blocking — nice, surgical PR.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
spec/command/cleanup_stale_apps_spec.rb (1)

132-141: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Adapt test to allow optional additional_workloads.

If additional_workloads is relaxed to be an optional configuration key as suggested in lib/command/cleanup_stale_apps.rb, this test should be updated to verify that the method succeeds (or falls back to an empty array) rather than expecting it to raise an error.

♻️ Proposed test update
-    it "raises when the stale app config does not define additional_workloads" do
+    it "succeeds when the stale app config does not define additional_workloads" do
       allow(config).to receive(:find_app_config)
         .with("stale-app")
         .and_return({ app_workloads: ["rails"] })

-      expect { command.send(:process_app, "stale-app") }
-        .to raise_error("Can't find option 'additional_workloads' for app 'stale-app' in 'controlplane.yml'.")
-      expect(cp).not_to have_received(:fetch_workloads)
-      expect(command).not_to have_received(:run_cpflow_command)
+      command.send(:process_app, "stale-app")
+
+      expect(cp).to have_received(:fetch_workloads)
     end
🤖 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 `@spec/command/cleanup_stale_apps_spec.rb` around lines 132 - 141, Update the
“raises when the stale app config…” example around command.send(:process_app,
"stale-app") to reflect optional additional_workloads: expect processing to
succeed and use an empty workload list when the key is absent, while preserving
assertions that no workload fetch or cpflow command occurs.
🤖 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.

Nitpick comments:
In `@spec/command/cleanup_stale_apps_spec.rb`:
- Around line 132-141: Update the “raises when the stale app config…” example
around command.send(:process_app, "stale-app") to reflect optional
additional_workloads: expect processing to succeed and use an empty workload
list when the key is absent, while preserving assertions that no workload fetch
or cpflow command occurs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 643b4f88-6ab0-4d0c-8fe5-863c3c51a971

📥 Commits

Reviewing files that changed from the base of the PR and between 26bdb56 and 5309f20.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • lib/command/cleanup_stale_apps.rb
  • lib/command/copy_image_from_upstream.rb
  • lib/command/deploy_image.rb
  • lib/core/controlplane.rb
  • spec/command/build_image_spec.rb
  • spec/command/cleanup_stale_apps_spec.rb
  • spec/command/copy_image_from_upstream_unit_spec.rb
  • spec/command/deploy_image_unit_spec.rb
  • spec/core/controlplane_spec.rb

@justin808

Copy link
Copy Markdown
Member Author

Address review is complete at exact head 5309f208028a4db68e3ee54d160042251eb1438b.

Implemented findings:

  • restricted the retry boundary to the workload image update so endpoint lookup is never retried after a successful update (1b1c403)
  • restored validation of both required workload keys before any Control Plane read and added an explicit shellwords require (5309f208)

Verified decisions:

  • bounded retry of permanent failures is an accepted tradeoff at the boolean command seam
  • explicit-nil behavior preserves the pre-existing Config#[] contract; broader schema validation is outside Scheduled slow suite failing #409
  • live-but-unconfigured workloads are deliberately skipped and documented; per-workload logging is optional UX
  • retrying a signal-killed child matches existing perform/perform! semantics
  • CodeRabbit's suggestion to make additional_workloads optional was rejected because it conflicts with the existing required-key contract

Skipped as non-actionable: the unreachable ambiguous-prefix scenario and unchanged endpoint-nil semantics. All nine review threads are now resolved, and the independent exact-head code audit is clean.

Merge remains blocked only by external GVC capacity: exact-head run 29492232103, artifact 8373278529, failed 13 live-fixture examples after 39 setup attempts were rejected at the org's 20/20 quota. No destructive cleanup was performed.

@justin808

justin808 commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

Canonical blocked-batch handoff for cpf-slow-suite-20260715-2057 / CPF-C-issue409-reef.

Immediate maintainer attention

  • Final state: external-gate-failing at exact head 5309f208028a4db68e3ee54d160042251eb1438b.
  • Required operator action: separately authorize cleanup of stale CI GVCs in shakacode-heroku-to-control-plane-ci, or have Control Plane raise the GVC quota above 20.
  • Resume gate: rerun Fast at this exact head, trigger the relevant manual/scheduled Slow workflow at the same SHA, rescan current-head comments/checks, then merge only when every gate is green.
  • Post-merge gate: replay the appropriate workflow on current main; close Scheduled slow suite failing #409 only with linked passing evidence.

FYI / decisions made

  • Code/review result: clean; all nine current review threads resolved.
  • Hosted result: only RSpec (Fast) / rspec is red. Run 29492232103 and artifact 8373278529 show 39 HTTP 429 GVC-quota rejections causing all 13 failed examples.
  • Safety boundary: no live GVC cleanup, deployment, or other destructive mutation was performed.
  • Merge authority: auto_merge_when_gates_pass; merge was correctly withheld because the full gate is not green.
  • Merge ledger: n/a per repository AGENTS.md.
  • Issue state: Scheduled slow suite failing #409 remains open because there is no passing Fast and Slow replay.

Independent checker verdict: BLOCKED_EXTERNAL, not implementation-blocked.

Target stale-app stops to configured live workloads, contain upstream authorization failures inside the progress step, and retry transient workload image updates before reporting endpoints. Update the build spec to compare shell tokens instead of raw escaping.
@justin808
justin808 force-pushed the codex/409-scheduled-slow-suite branch from 5309f20 to 261d849 Compare July 18, 2026 11:20
Comment thread lib/command/cleanup_stale_apps.rb
Comment thread lib/command/deploy_image.rb
Comment thread CHANGELOG.md
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Summary

This PR fixes four independent regressions surfaced by the scheduled slow-suite run for issue #409. Each fix is narrowly scoped and backed by new/updated unit tests. Overall the change is well-reasoned and low-risk; I traced through the control flow for each fix and did not find correctness bugs, only a few things worth being aware of.

What the PR does

  • cleanup-stale-apps --mode=stop: now stops only the intersection of configured (app_workloads + additional_workloads) and live workloads, instead of blindly running ps:stop -a app, avoiding failures when a configured workload was already removed.
  • copy-image-from-upstream: catches ControlplaneApiDirect::ForbiddenError inside the fetch_upstream_image_url step so it is reported as a clean "failed!" step message instead of escaping to the generic "Unable to load command: ..." handler in lib/cpflow.rb (confirmed ForbiddenError < StandardError, not RuntimeError, so it previously bypassed step's rescue and Cli's inner rescue RuntimeError).
  • deploy-image: Controlplane#workload_set_image_ref switched from perform! to perform, and the call site now wraps it in with_retry (30 attempts, 1s wait) so eventually-consistent image-update failures get retried before the endpoint is reported.
  • build_image_spec.rb: slow-suite assertion now parses the actual Process.spawn command with Shellwords.split and checks token adjacency instead of matching a raw regex against escaped output.

Correctness

  • Verified workload_set_image_ref is only called from this one site, so the perform! to perform change is safely contained.
  • Verified the upstream-profile cleanup ensure block in copy_image_from_upstream.rb#call already ran regardless of exception type before this PR, so the real fix here is error-message quality/exit path, not profile leakage -- the PR description slightly overstates this, but the fix itself is correct and worthwhile.
  • The configured_workloads & live_workloads intersection correctly implements "skip configured-but-removed, skip live-but-unconfigured," matching the updated LONG_DESCRIPTION.
  • Confirmed the new --workload flag usage matches ps_stop.rb's existing workload_option.

Minor points (posted inline)

  • cleanup_stale_apps.rb: stopping workloads one-by-one via run_cpflow_command now spawns a full CLI invocation per workload (was one call total before). Correct given ps:stop's workload option is not repeatable, but worth knowing the overhead scales with workload count.
  • deploy_image.rb: since perform cannot distinguish transient vs. permanent failures, the 30s retry window now also delays reporting deterministic failures (bad image ref, permissions, etc.). Already acknowledged in the PR's decision log as an accepted tradeoff.
  • CHANGELOG.md: the new entry does not include a PR link/author credit, unlike the entry directly above it.
  • Small duplication: required_app_option in cleanup_stale_apps.rb re-implements the same "raise unless key present" pattern as Config#[] (lib/core/config.rb:88-92), just parameterized over an arbitrary app instead of the command's own config.app. Not a bug, just a note in case a shared helper is ever worth extracting.

Test coverage

Good coverage of the new behavior: workload-filtering (including both "missing required option" error paths), the forbidden-error/profile-cleanup path (with an explicit assertion that the token itself is never printed), and all three retry outcomes for the image-update path (success-after-retry, endpoint-resolution failure after a successful update, and exhausting the retry budget). The build_image_spec.rb fix is a reasonable, self-contained test-only correction.

Security

No concerns -- the upstream-token handling change actually improves things (test explicitly asserts the token never appears in printed output), and no new user input is passed unsanitized to shell commands.

No blocking issues found.

@justin808

Copy link
Copy Markdown
Member Author

Address-review summary

Scan scope: since previous summary at 2026-07-16T11:10:42Z.

Mattered

  • None. The exact-head review produced no correctness, security, reliability, or test blocker.

Optional

  • Per-workload CLI overhead — auto-deferred/declined at the final-candidate gate. Verified that ps:stop accepts one workload, so a repeatable interface would expand scope; the current configured-and-live intersection is intentional.
  • Changelog PR/author metadata — auto-deferred/declined at the final-candidate gate. The current entry has replayable issue linkage and accurate behavior; a nit-only push would restart exact-head Fast and Slow integration gates.

Skipped

  • Bounded retry also covers deterministic failures — informational and already documented as an accepted tradeoff. The boolean-only command seam cannot safely classify failure causes; the 30-attempt bound and nonzero terminal result are tested.

All three exact-head threads were replied to and resolved. No branch change was made; candidate SHA remains 261d849efbdcc6d120a93c268f8d3bdb24f79fde.

Next default scan starts after this comment. Say check all reviews to rescan the full PR.

@justin808

Copy link
Copy Markdown
Member Author

Exact-head Slow replay classification for 261d849efbdcc6d120a93c268f8d3bdb24f79fde:

  • Manual run 29642419690, Slow job 88075006379, advanced for six hours and was canceled by GitHub during Command::PsStop; its preserved artifact is spec-slow-29642419690-ubuntu-latest-3.2.log.
  • The concurrent Fast job recorded repeated HTTP 429 responses: the shared org had reached its 20-GVC quota. Slow created dummy-test-full-a9de, but its initial image build failed and the Rails workload remained on NO_IMAGE_AVAILABLE; readiness-dependent examples then exhausted their ten-minute per-example budgets (32 failures before cancellation).
  • Control comparison: scheduled current-main run 29631251499 completed all 82 Slow examples in 90m30s, including both PsRestart examples. This PR changes no production build-image or process-start path. The six-hour result is therefore classified as concurrent shared-org/live-setup fallout, not a candidate regression.
  • GitHub cancellation bypassed RSpec after-suite cleanup. Three run-scoped GVCs remain (dummy-test-full-a9de, dummy-test-rails-non-app-image-a9de, dummy-test-rails-env-a9de). I have not deleted them because destructive live cleanup is not authorized.

Merge remains blocked. Per the batch contract, I will rerun only the failed PR Fast job after these Slow resources are released and capacity is clear, then obtain a clean exact-head Slow replay before merging.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 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 `@lib/command/cleanup_stale_apps.rb`:
- Around line 114-115: Update the configured_workloads assignment to normalize
nil results from required_app_option for both :app_workloads and
:additional_workloads to empty arrays before concatenation, using Array(...) or
an equivalent fallback. Preserve concatenation of configured workload arrays
when values are present.
🪄 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: 21925559-c4a4-4fbd-a8e9-809ab4803cb2

📥 Commits

Reviewing files that changed from the base of the PR and between 5309f20 and f1ae55a.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • lib/command/cleanup_stale_apps.rb
  • lib/command/copy_image_from_upstream.rb
  • lib/command/deploy_image.rb
  • lib/core/controlplane.rb
  • spec/command/build_image_spec.rb
  • spec/command/cleanup_stale_apps_spec.rb
  • spec/command/copy_image_from_upstream_unit_spec.rb
  • spec/command/deploy_image_unit_spec.rb
  • spec/command/run_spec.rb
  • spec/core/controlplane_spec.rb
🚧 Files skipped from review as they are similar to previous changes (9)
  • lib/core/controlplane.rb
  • spec/core/controlplane_spec.rb
  • spec/command/build_image_spec.rb
  • spec/command/deploy_image_unit_spec.rb
  • spec/command/cleanup_stale_apps_spec.rb
  • lib/command/copy_image_from_upstream.rb
  • spec/command/copy_image_from_upstream_unit_spec.rb
  • lib/command/deploy_image.rb
  • CHANGELOG.md

Comment thread lib/command/cleanup_stale_apps.rb
Comment thread lib/command/cleanup_stale_apps.rb
Comment thread lib/command/cleanup_stale_apps.rb
Comment thread lib/command/deploy_image.rb
@claude

claude Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review

Scoped, well-tested fix for the four scheduled slow-suite regressions from #409. Traced each change against its rationale and existing conventions (e.g. the step/with_retry/ForbiddenError handling mirrors the documented pattern in lib/core/maintenance_mode.rb); didn't find any correctness or security blockers.

What it does

  • cleanup-stale-apps --mode=stop now stops only the intersection of configured (app_workloads + additional_workloads) and live workloads, instead of blindly stopping all configured workloads (which failed if one had already been removed from the live GVC).
  • copy-image-from-upstream explicitly rescues ControlplaneApiDirect::ForbiddenError inside the fetch_upstream_image_url step so the failure reports cleanly through step's "failed!" output instead of propagating as an unhandled StandardError (step's own rescue only catches RuntimeError).
  • deploy-image now retries workload_set_image_ref up to 30 times (1s apart) and only records/deploys an endpoint after a successful update; workload_set_image_ref switched from perform! (hard abort) to perform (boolean) to make this retryable.
  • The build-image slow spec now compares parsed Shellwords tokens instead of a raw regex against the escaped command string.

Inline notes posted (all minor, non-blocking):

  • cleanup_stale_apps.rb: required_app_option only checks key presence, not that the value is an array — an explicit nil in config would raise a raw NoMethodError instead of the intended friendly error.
  • cleanup_stale_apps.rb: stopping workloads one-by-one via run_cpflow_command (since ps:stop --workload isn't repeatable) re-runs full CLI validation per workload — likely fine given the narrow scope of this fix, but a candidate for a follow-up if ps:stop ever supports multiple --workload flags.
  • deploy_image.rb: worth a short code comment noting that workload_set_image_ref's retry can't distinguish transient vs. permanent failures, so a deterministic failure still burns the full ~30s retry window (already called out in the PR's decision log, just not in the code).

Test coverage: solid — new specs cover the stale-workload intersection logic (including both missing-config-key error paths), the forbidden-upstream-token flow (and confirms the token itself isn't leaked to output), and all three retry outcomes (immediate success-after-retry, exhausted retries, and endpoint-resolution failure not re-triggering the image update).

No security issues found — the ForbiddenError message surfaced to output doesn't include the upstream token, and behavior changes are confined to their stated seams.

@justin808

Copy link
Copy Markdown
Member Author

Address-review summary

Scan scope: since previous summary at 2026-07-18T11:30:20Z.

Mattered

  • None. Current-head Fast, RuboCop, docs, links, Claude review, security preflight, autoreview, and the independent full-diff review are clean at f1ae55a1532dace00f29340543a4cd24f146da7a.

Optional

Skipped

All four newly opened threads were replied to and resolved. No branch change was made; candidate SHA remains f1ae55a1532dace00f29340543a4cd24f146da7a.

Next default scan starts after this comment. Say check all reviews to rescan the full PR.

@justin808

Copy link
Copy Markdown
Member Author

Address-review summary

Scan scope: since previous summary at 2026-07-19T08:18:11Z.

Mattered

  • None. No new actionable review feedback was present in the current inventory.

Skipped

  • None.

The fresh claude-review job was still running after the bounded three-minute wait. Any feedback it posts will be newer than this checkpoint and remains eligible for the required pre-merge rescan.

Next default scan starts after this comment. Say check all reviews to rescan the full PR.

Comment thread lib/command/deploy_image.rb
Comment thread lib/command/cleanup_stale_apps.rb
@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review: Fix scheduled slow-suite regressions (#413)

Overview

This PR fixes four regressions surfaced by the scheduled slow suite (issue #409):

  1. cleanup-stale-apps --mode=stop now stops only workloads that are both configured (app_workloads/additional_workloads) and still live in the GVC, instead of blindly calling ps:stop -a app (which could fail on a workload removed from config).
  2. copy-image-from-upstream now rescues ControlplaneApiDirect::ForbiddenError inside the "Fetching upstream image URL" step, so an invalid upstream token/org produces a clean failure and the temporary profile is still deleted (via the existing ensure), instead of an unhandled exception leaking past the profile cleanup.
  3. deploy-image retries a transient workload_set_image_ref failure up to 30 times (1s apart) via with_retry, and only records a workload's endpoint after a successful update — achieved by switching Controlplane#workload_set_image_ref from perform! (immediate abort) to perform (boolean result).
  4. The build-image spec now parses the spawned command with Shellwords.split and checks token pairs instead of a raw regex, fixing a matcher that broke when the underlying shell-escaping changed.

Code quality

  • Changes are narrowly scoped to the four described seams, consistent with the PR's stated intent — no unrelated refactors.
  • New/changed logic is well covered by unit specs (cleanup_stale_apps_spec.rb, new copy_image_from_upstream_unit_spec.rb, deploy_image_unit_spec.rb, controlplane_spec.rb), including both the happy path and config-validation error paths.
  • Controlplane#workload_set_image_ref's contract change (perform!perform) has exactly one call site (deploy_image.rb), so there's no risk of silently changing behavior for other callers.
  • Verified that stop_configured_live_workloads's hard-fail on missing app_workloads/additional_workloads isn't actually a new failure mode: ps:stop (the old code path) reads the same config keys via Config#[] and would have raised identically one level down, inside the spawned subcommand.

Security

  • The new ForbiddenError rescue writes e.message to stderr; verified the message (built in ControlplaneApiDirect::ForbiddenError#initialize) only includes the org name and HTTP response, never the token — and the new spec explicitly asserts the token string never appears in output. No credential leakage.
  • No new shell-command construction or string interpolation into cpln/docker commands beyond what already existed.

Minor, non-blocking notes (posted inline)

  • deploy_image.rb: since permanent (non-transient) image-update failures now retry the full 30-attempt window before failing, they may print the raw cpln CLI error repeatedly (visible under :errors_only output mode) and take ~30s to surface — an accepted tradeoff per the PR description, just flagging the UX impact.
  • cleanup_stale_apps.rb: required_app_option duplicates the same required-key-check pattern as Config#[] (can't reuse directly since it needs an arbitrary app name rather than the invocation's own -a app) — a shared helper could avoid the two error strings drifting apart later.

Test coverage

Good — the PR adds targeted regression tests for all four fixes, including negative/error-path cases (missing config keys, forbidden upstream token, bounded-retry exhaustion). No test coverage gaps identified for the described scope.

No blocking issues found.

@justin808

Copy link
Copy Markdown
Member Author

Address-review summary

Scan scope: since previous summary at 2026-07-22T00:17:45Z.

Mattered

  • None. The fresh exact-head review reported no correctness, security, or coverage blocker at 7cb1f8ac2443cb1b26dd0e1c6fe6b8020dc077e7.

Optional

  • Bounded retry UX — auto-deferred/declined. Distinguishing permanent from transient CLI failures requires a broader command/API seam; the original 30-attempt cap is restored and independently reviewed.
  • Required-key helper DRY refactor — auto-deferred/declined. Expanding Config for arbitrary-app lookup is polish outside Scheduled slow suite failing #409; current behavior preserves the prior error contract and is tested.

Skipped

  • Fresh Claude review summary — status-only; it explicitly reports no blocking issues and its two inline notes are accounted for above.

Both fresh threads were replied to and resolved. No branch change was made; candidate SHA remains 7cb1f8ac2443cb1b26dd0e1c6fe6b8020dc077e7.

Next default scan starts after this comment. Say check all reviews to rescan the full PR.

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.

Scheduled slow suite failing

1 participant