Skip to content

feat(ci): promote Trigger.dev tasks in lockstep with the ECS traffic cutover#5725

Open
TheodoreSpeaks wants to merge 1 commit into
stagingfrom
trigger-deploy
Open

feat(ci): promote Trigger.dev tasks in lockstep with the ECS traffic cutover#5725
TheodoreSpeaks wants to merge 1 commit into
stagingfrom
trigger-deploy

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • Move Trigger.dev staging + production deploys into CI (off the git-integration auto-deploy), deploying with --skip-promotion so new task runs keep executing the old version until we promote.
  • Promote the deployed version at the exact ECS traffic cutover: a new promote-trigger-* job polls CodeDeploy (AllowTraffic) and then runs trigger promote, so tasks and app cut over in lockstep instead of tasks flipping ~4–50 min early (the prod bake queue).
  • Correlate this push to its ECS deploy via the app image digest (build-amd64 publishes it as an artifact) → CodePipeline execution → CodeDeploy deployment id → AllowTraffic. ECR deploy tags are floating, so the digest is the only durable key.
  • Gate promotion on promote-images.promoted: when the stale-run guard skips the image retag, tasks correctly stay on the old version. If the app deploy fails or never cuts over, promote never fires and the job fails visibly.
  • New .github/scripts/wait-for-ecs-cutover.sh does the poll; verified end-to-end against real staging deploys (digest → d-…AllowTraffic).

Prerequisites before this runs on staging/prod

  • Add CodePipeline + CodeDeploy read perms to the OIDC deploy roles (STAGING_AWS_ROLE_TO_ASSUME, AWS_ROLE_TO_ASSUME), which are managed outside the repo: codepipeline:ListPipelineExecutions/GetPipelineExecution/ListActionExecutions, codedeploy:GetDeployment/ListDeploymentTargets/GetDeploymentTarget.
  • Set the TRIGGER_ACCESS_TOKEN repo secret (a Trigger.dev PAT; spans all environments). Then disable the Trigger.dev git integration for staging/prod.

Type of Change

  • New feature (CI/CD)

Testing

  • Ran wait-for-ecs-cutover.sh against real completed staging deployments: correctly resolved digest → pipeline execution → CodeDeploy deployment → AllowTraffic (exit 0), and timed out cleanly on an unmatched digest (exit 1).
  • actionlint clean (only pre-existing custom-runner-label warnings); bun run lint and bun run check:api-validation:strict pass; YAML validated.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 17, 2026 1:38am

Request Review

@cursor

cursor Bot commented Jul 17, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes production/staging release orchestration and timing between ECS and Trigger.dev; mis-correlation or premature promotion could cause app/task version skew until IAM prerequisites and git-integration disable are in place.

Overview
Staging and production Trigger.dev deploys move into CI: tasks are uploaded with --skip-promotion after migrate (parallel to image build), and the deployed version string is captured for a later promote step.

promote-images now exposes a promoted output from the stale-run guard so task promotion is skipped when the app deploy tag never moved.

build-amd64 publishes the app image digest as a CI artifact; promote-trigger-staging / promote-trigger-production download it, run wait-for-ecs-cutover.sh against the environment’s CodePipeline until CodeDeploy AllowTraffic succeeds, then run trigger promote for that version—so background tasks flip with the ECS app cutover instead of minutes earlier.

New wait-for-ecs-cutover.sh correlates digest → pipeline execution → CodeDeploy deployment and polls with a ~70m timeout for prod bake queues.

Reviewed by Cursor Bugbot for commit 30e1270. Bugbot is set up for automated code reviews on this repo. Configure here.

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 30e1270. Configure here.

Comment thread .github/workflows/ci.yml
name: app-image-digest
path: digest/app-image-digest.txt
retention-days: 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ECS deploy before Trigger deploy

High Severity

promote-images only depends on migrate and build-amd64, not deploy-trigger-staging or deploy-trigger-production. It can retag ECR and start CodePipeline while the Trigger.dev skip-promotion deploy is still running or after it failed, so the app can cut over without a matching task version or with a long app–task skew.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 30e1270. Configure here.

sleep "$POLL_INTERVAL"
fi
done
log "Matched pipeline execution: $EXECUTION_ID"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale pipeline digest match

Medium Severity

Phase A picks any pipeline execution whose ECR source revisionId equals the digest, using the first UUID returned. A prior completed run for the same image digest can match before the new execution exists, so AllowTraffic may already be Succeeded and Trigger.dev promotion runs immediately instead of waiting for this push’s cutover.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 30e1270. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR moves Trigger.dev task promotion into the ECS deployment flow. The main changes are:

  • Deploy staging and production task versions without immediate promotion.
  • Publish the application image digest as a workflow artifact.
  • Resolve the matching CodeDeploy deployment and wait for AllowTraffic.
  • Promote the task version only after the image guard and traffic cutover.

Confidence Score: 4/5

The cutover gate can accept an old deployment or a partially completed multi-target deployment.

  • Same-run task version propagation and stale image guards are wired consistently.
  • A reused image digest can match historical pipeline state and promote tasks early.
  • Checking only the first ECS target can promote tasks during a partial cutover.

.github/scripts/wait-for-ecs-cutover.sh

Important Files Changed

Filename Overview
.github/scripts/wait-for-ecs-cutover.sh Adds deployment polling, but digest reuse can select an old execution and only the first deployment target is checked.
.github/workflows/ci.yml Adds task deployment, digest artifact propagation, stale-run gating, and environment-specific promotion jobs.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CI as GitHub CI
    participant Trigger as Trigger.dev
    participant ECR as Amazon ECR
    participant Pipeline as CodePipeline
    participant Deploy as CodeDeploy

    CI->>Trigger: Deploy version without promotion
    Trigger-->>CI: Return version
    CI->>ECR: Move staging/latest tag
    ECR->>Pipeline: Start deployment execution
    CI->>Pipeline: Find execution by image digest
    Pipeline-->>CI: Return CodeDeploy ID
    loop Until AllowTraffic succeeds
        CI->>Deploy: Read ECS target lifecycle status
        Deploy-->>CI: Return AllowTraffic status
    end
    CI->>Trigger: Promote version
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CI as GitHub CI
    participant Trigger as Trigger.dev
    participant ECR as Amazon ECR
    participant Pipeline as CodePipeline
    participant Deploy as CodeDeploy

    CI->>Trigger: Deploy version without promotion
    Trigger-->>CI: Return version
    CI->>ECR: Move staging/latest tag
    ECR->>Pipeline: Start deployment execution
    CI->>Pipeline: Find execution by image digest
    Pipeline-->>CI: Return CodeDeploy ID
    loop Until AllowTraffic succeeds
        CI->>Deploy: Read ECS target lifecycle status
        Deploy-->>CI: Return AllowTraffic status
    end
    CI->>Trigger: Promote version
Loading

Reviews (1): Last reviewed commit: "feat(ci): promote Trigger.dev tasks in l..." | Re-trigger Greptile

Comment on lines +43 to +46
EXECUTION_ID=$(aws codepipeline list-pipeline-executions \
--pipeline-name "$PIPELINE" --max-items 30 \
--query "pipelineExecutionSummaries[?sourceRevisions[?actionName=='ECR_Source' && revisionId=='$DIGEST']].pipelineExecutionId" \
--output text 2>/dev/null | tr '\t ' '\n\n' | grep -Em1 '^[0-9a-f-]{36}$' || true)

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 Historical Deployment Satisfies Gate

When a previous deployment has the same image digest, this lookup can select that completed execution before the current tag push creates its execution. The old deployment already has a successful AllowTraffic, so the new Trigger.dev version is promoted before the current ECS cutover. Correlate the execution with this tag-promotion event, such as by requiring a start time after the retag, rather than accepting any historical digest match.

Comment on lines +91 to +99
target_id=$(aws deploy list-deployment-targets --deployment-id "$DEPLOYMENT_ID" \
--query 'targetIds[0]' --output text 2>/dev/null || true)
at_status=""
if [ -n "$target_id" ] && [ "$target_id" != "None" ]; then
at_status=$(aws deploy get-deployment-target --deployment-id "$DEPLOYMENT_ID" --target-id "$target_id" \
--query "deploymentTarget.ecsTarget.lifecycleEvents[?lifecycleEventName=='AllowTraffic'].status | [0]" \
--output text 2>/dev/null || true)
if [ "$at_status" = "Succeeded" ]; then
log "Traffic cutover complete (AllowTraffic Succeeded) for $DEPLOYMENT_ID"

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 Only First Target Gates Promotion

When a CodeDeploy deployment contains multiple ECS targets, targetIds[0] can finish AllowTraffic while another target is still pending or has failed. The script then promotes Trigger.dev while part of the application remains on the old version. Require successful AllowTraffic for every returned ECS target before exiting.

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.

1 participant