#1801 was fixed for Azure in #1803. Both halves of that bug are still live on GCP and AWS. Filed separately because the scope of #1803 was Azure and the fix is not a copy-paste.
Half 1: branch-keyed concurrency against environment-keyed state
deploy-aws-lambda.yml:33-35 -> group: deploy-lambda-${{ github.ref }}
deploy-aws-fargate.yml:28-30 -> group: deploy-fargate-${{ github.ref }}
deploy-gcp.yml:27-29 -> group: deploy-gcp-${{ github.ref }}
Each state key is built from the environment, so two runs on different refs that resolve to the same environment land in different concurrency groups and apply against one state file. Identical to the Azure defect.
Half 2: unconditional destruction of a live lock
Both steps run on if: failure() || cancelled() with no age check and no check that the lock is anyone else's:
deploy-gcp.yml:156-167
LOCK_FILE="gs://${BUCKET}/github-${ENVIRONMENT}/default.tflock"
gsutil rm "${LOCK_FILE}" 2>/dev/null || echo "No lock file found (already clean)"
deploy-aws-lambda.yml:269-280
LOCK_KEY="github-${ENVIRONMENT}/terraform.tfstate.tflock"
aws s3 rm "s3://${BUCKET}/${LOCK_KEY}" 2>/dev/null || echo "No lock file found (already clean)"
Deleting another run's live lock object is exactly as destructive as breaking its lease. The lock mechanism differs from Azure's blob lease; the failure mode does not. Combined with half 1, this reproduces #1801's silent state loss: the second writer removes the first's lock, both proceed, and the later writer's stale read drops the earlier one's entries.
An earlier draft of #1803's description claimed the silent-loss half did not transfer to AWS because S3 uses a lock file rather than a lease. That was wrong and is corrected there.
Why this is not a copy-paste of #1803
Suggested approach
Follow #1803: move each group to the job that writes state, key it on the same value that builds the state key, apply the identical group to every workflow writing that state, and delete the lock-removal steps so a real collision fails loudly.
#1801 was fixed for Azure in #1803. Both halves of that bug are still live on GCP and AWS. Filed separately because the scope of #1803 was Azure and the fix is not a copy-paste.
Half 1: branch-keyed concurrency against environment-keyed state
deploy-aws-lambda.yml:33-35->group: deploy-lambda-${{ github.ref }}deploy-aws-fargate.yml:28-30->group: deploy-fargate-${{ github.ref }}deploy-gcp.yml:27-29->group: deploy-gcp-${{ github.ref }}Each state key is built from the environment, so two runs on different refs that resolve to the same environment land in different concurrency groups and apply against one state file. Identical to the Azure defect.
Half 2: unconditional destruction of a live lock
Both steps run on
if: failure() || cancelled()with no age check and no check that the lock is anyone else's:deploy-gcp.yml:156-167deploy-aws-lambda.yml:269-280Deleting another run's live lock object is exactly as destructive as breaking its lease. The lock mechanism differs from Azure's blob lease; the failure mode does not. Combined with half 1, this reproduces #1801's silent state loss: the second writer removes the first's lock, both proceed, and the later writer's stale read drops the earlier one's entries.
An earlier draft of #1803's description claimed the silent-loss half did not transfer to AWS because S3 uses a lock file rather than a lease. That was wrong and is corrected there.
Why this is not a copy-paste of #1803
deploy-aws-lambda.ymlderives its environment differently (target_environment, and it already handles the reusable-workflowgithub.event_namequirk that Reusable deploy workflows read github.event_name as workflow_call, silently downgrading a release deploy to dev #1805 covers).github-fargate-<env>/), so it needs its own group, not the lambda one.cleanup-staging.ymlandrollback.ymlalso write the AWS and GCP states and would need the matching groups, same as the Azure change.Suggested approach
Follow #1803: move each group to the job that writes state, key it on the same value that builds the state key, apply the identical group to every workflow writing that state, and delete the lock-removal steps so a real collision fails loudly.