From 4cf9a8286d8a7244082f649cbdee47490e80d4d9 Mon Sep 17 00:00:00 2001 From: Jean du Plessis Date: Fri, 17 Jul 2026 10:22:40 +0200 Subject: [PATCH 1/2] ci(deploy): annotate production deploys in Axiom Add a best-effort Axiom annotation step to the shared promote workflow so production deploys of kilocode-app show up alongside metrics/traces. - Adds axiom_annotation_dataset input (opt-in per caller) and axiom_expected_project input to keep the reusable workflow generic. - Wires deploy-production.yml and redeploy-web.yml to pass axiom_annotation_dataset: kilocode-app for the app promotion job. - Verifies the promoted deployment matches the expected project/target/ READY state before annotating, retrying briefly to absorb Vercel API propagation lag. - Marked continue-on-error so a flaky Axiom/Vercel call never fails a promotion that already succeeded. --- .github/workflows/deploy-production.yml | 15 ++-- .../workflows/promote-vercel-deployment.yml | 87 ++++++++++++++++++- .github/workflows/redeploy-web.yml | 1 + 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index 42cea67f36..f19b5c7600 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -13,7 +13,7 @@ concurrency: cancel-in-progress: false env: - VERCEL_VERSION: '53.3.1' + VERCEL_VERSION: "53.3.1" VERCEL_ORG_ID: ${{ vars.VERCEL_ORG_ID }} jobs: @@ -38,8 +38,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version-file: '.nvmrc' - cache: 'pnpm' + node-version-file: ".nvmrc" + cache: "pnpm" - name: Install dependencies run: pnpm install --frozen-lockfile @@ -68,8 +68,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version-file: '.nvmrc' - cache: 'pnpm' + node-version-file: ".nvmrc" + cache: "pnpm" - name: Install dependencies run: pnpm install --frozen-lockfile @@ -96,6 +96,7 @@ jobs: uses: ./.github/workflows/promote-vercel-deployment.yml with: deployment_url: ${{ needs.stage-app.outputs.deployment_url }} + axiom_annotation_dataset: kilocode-app secrets: inherit promote-global-app: @@ -267,8 +268,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version-file: '.nvmrc' - cache: 'pnpm' + node-version-file: ".nvmrc" + cache: "pnpm" - name: Install dependencies run: pnpm install --frozen-lockfile diff --git a/.github/workflows/promote-vercel-deployment.yml b/.github/workflows/promote-vercel-deployment.yml index 3eeb70f3c3..8689f1a90e 100644 --- a/.github/workflows/promote-vercel-deployment.yml +++ b/.github/workflows/promote-vercel-deployment.yml @@ -4,9 +4,19 @@ on: workflow_call: inputs: deployment_url: - description: 'Vercel deployment URL to promote' + description: "Vercel deployment URL to promote" required: true type: string + axiom_annotation_dataset: + description: "Optional Axiom dataset used to scope deployment annotations" + required: false + type: string + default: "" + axiom_expected_project: + description: "Vercel project name the promoted deployment must match before annotating" + required: false + type: string + default: "kilocode-app" permissions: contents: read @@ -18,7 +28,7 @@ jobs: environment: production env: - VERCEL_VERSION: '53.3.1' + VERCEL_VERSION: "53.3.1" steps: - name: Install Vercel CLI @@ -26,3 +36,76 @@ jobs: - name: Promote Vercel deployment run: vercel promote "${{ inputs.deployment_url }}" --scope=kilocode --token=${{ secrets.VERCEL_TOKEN }} + + - name: Annotate production deployment in Axiom + if: inputs.axiom_annotation_dataset != '' + # Annotation is best-effort observability, not a deploy gate: never fail + # a successful promotion because Axiom/Vercel is flaky or slow to update. + continue-on-error: true + env: + AXIOM_ANNOTATION_TOKEN: ${{ secrets.AXIOM_ANNOTATION_TOKEN }} + AXIOM_DATASET: ${{ inputs.axiom_annotation_dataset }} + AXIOM_ORG_ID: kilo-code-nvjh + DEPLOYMENT_URL: ${{ inputs.deployment_url }} + EXPECTED_PROJECT: ${{ inputs.axiom_expected_project }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + run: | + if [ -z "$AXIOM_ANNOTATION_TOKEN" ]; then + echo "::warning::Skipping deployment annotation because AXIOM_ANNOTATION_TOKEN is not configured" + exit 0 + fi + + # Vercel's API can briefly lag behind a promotion, so retry until the + # deployment reports READY instead of failing on the first check. + deployment="" + for attempt in 1 2 3 4 5; do + inspection=$(vercel inspect "$DEPLOYMENT_URL" --json --scope=kilocode --token="$VERCEL_TOKEN" 2>&1) + deployment_id=$(jq -r '.id // empty' <<<"$inspection" 2>/dev/null) + if [ -z "$deployment_id" ]; then + echo "::warning::vercel inspect did not return a deployment id (attempt $attempt): $inspection" + sleep 5 + continue + fi + + deployment=$(vercel api "/v13/deployments/$deployment_id" --raw --scope=kilocode --token="$VERCEL_TOKEN" 2>&1) + ready_state=$(jq -r '.readyState // empty' <<<"$deployment" 2>/dev/null) + if [ "$ready_state" = "READY" ]; then + break + fi + + echo "::warning::Deployment not READY yet (attempt $attempt, state=$ready_state)" + sleep 5 + done + + project=$(jq -r '.name // empty' <<<"$deployment" 2>/dev/null) + target=$(jq -r '.target // empty' <<<"$deployment" 2>/dev/null) + ready_state=$(jq -r '.readyState // empty' <<<"$deployment" 2>/dev/null) + + if [ "$project" != "$EXPECTED_PROJECT" ] || [ "$target" != "production" ] || [ "$ready_state" != "READY" ]; then + echo "::warning::Skipping annotation for unexpected deployment: project=$project target=$target state=$ready_state" + exit 0 + fi + + deployment_time=$(jq -r '((.ready // .createdAt) / 1000) | todateiso8601' <<<"$deployment") + commit_sha=$(jq -r '.meta.githubCommitSha // "unknown"' <<<"$deployment") + short_sha=${commit_sha:0:8} + commit_message=$(jq -r '(.meta.githubCommitMessage // "Commit message unavailable" | split("\n")[0])[0:360]' <<<"$deployment") + commit_ref=$(jq -r '.meta.githubCommitRef // "unknown"' <<<"$deployment") + commit_author=$(jq -r '.meta.githubCommitAuthorName // .creator.username // "unknown"' <<<"$deployment") + deployment_link="https://vercel.com/kilocode/kilocode-app/${deployment_id#dpl_}" + + payload=$(jq -n \ + --arg dataset "$AXIOM_DATASET" \ + --arg time "$deployment_time" \ + --arg title "kilocode-app · $short_sha" \ + --arg description "$commit_message · $commit_ref · $commit_author" \ + --arg url "$deployment_link" \ + '{datasets:[$dataset], type:"kilocode-app-production-deployment", time:$time, title:$title, description:$description, url:$url}') + + curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $AXIOM_ANNOTATION_TOKEN" \ + --header "X-Axiom-Org-Id: $AXIOM_ORG_ID" \ + --header "Content-Type: application/json" \ + --data "$payload" \ + https://api.axiom.co/v2/annotations >/dev/null diff --git a/.github/workflows/redeploy-web.yml b/.github/workflows/redeploy-web.yml index 2f78936268..d87dec9eae 100644 --- a/.github/workflows/redeploy-web.yml +++ b/.github/workflows/redeploy-web.yml @@ -44,6 +44,7 @@ jobs: uses: ./.github/workflows/promote-vercel-deployment.yml with: deployment_url: ${{ needs.stage-app.outputs.deployment_url }} + axiom_annotation_dataset: kilocode-app secrets: inherit promote-global-app: From 349427e95365005ac6181a3598e1197bd39bc872 Mon Sep 17 00:00:00 2001 From: Jean du Plessis Date: Fri, 17 Jul 2026 10:30:43 +0200 Subject: [PATCH 2/2] fix(deploy): derive Axiom annotation project from EXPECTED_PROJECT, skip trailing retry sleep - Build the Axiom deployment link, title, and type from $project instead of hardcoding kilocode-app, so the annotation stays correct if a future caller passes a different axiom_expected_project. - Skip the retry sleep on the last loop attempt since it just delays the promotion job with no further use. - Run pnpm format to fix YAML quote-style formatting. --- .github/workflows/deploy-production.yml | 14 +++++----- .../workflows/promote-vercel-deployment.yml | 27 +++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index f19b5c7600..89cdcecccf 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -13,7 +13,7 @@ concurrency: cancel-in-progress: false env: - VERCEL_VERSION: "53.3.1" + VERCEL_VERSION: '53.3.1' VERCEL_ORG_ID: ${{ vars.VERCEL_ORG_ID }} jobs: @@ -38,8 +38,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version-file: ".nvmrc" - cache: "pnpm" + node-version-file: '.nvmrc' + cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile @@ -68,8 +68,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version-file: ".nvmrc" - cache: "pnpm" + node-version-file: '.nvmrc' + cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile @@ -268,8 +268,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version-file: ".nvmrc" - cache: "pnpm" + node-version-file: '.nvmrc' + cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile diff --git a/.github/workflows/promote-vercel-deployment.yml b/.github/workflows/promote-vercel-deployment.yml index 8689f1a90e..72c2121f4a 100644 --- a/.github/workflows/promote-vercel-deployment.yml +++ b/.github/workflows/promote-vercel-deployment.yml @@ -4,19 +4,19 @@ on: workflow_call: inputs: deployment_url: - description: "Vercel deployment URL to promote" + description: 'Vercel deployment URL to promote' required: true type: string axiom_annotation_dataset: - description: "Optional Axiom dataset used to scope deployment annotations" + description: 'Optional Axiom dataset used to scope deployment annotations' required: false type: string - default: "" + default: '' axiom_expected_project: - description: "Vercel project name the promoted deployment must match before annotating" + description: 'Vercel project name the promoted deployment must match before annotating' required: false type: string - default: "kilocode-app" + default: 'kilocode-app' permissions: contents: read @@ -28,7 +28,7 @@ jobs: environment: production env: - VERCEL_VERSION: "53.3.1" + VERCEL_VERSION: '53.3.1' steps: - name: Install Vercel CLI @@ -63,7 +63,9 @@ jobs: deployment_id=$(jq -r '.id // empty' <<<"$inspection" 2>/dev/null) if [ -z "$deployment_id" ]; then echo "::warning::vercel inspect did not return a deployment id (attempt $attempt): $inspection" - sleep 5 + if [ "$attempt" != "5" ]; then + sleep 5 + fi continue fi @@ -74,7 +76,9 @@ jobs: fi echo "::warning::Deployment not READY yet (attempt $attempt, state=$ready_state)" - sleep 5 + if [ "$attempt" != "5" ]; then + sleep 5 + fi done project=$(jq -r '.name // empty' <<<"$deployment" 2>/dev/null) @@ -92,15 +96,16 @@ jobs: commit_message=$(jq -r '(.meta.githubCommitMessage // "Commit message unavailable" | split("\n")[0])[0:360]' <<<"$deployment") commit_ref=$(jq -r '.meta.githubCommitRef // "unknown"' <<<"$deployment") commit_author=$(jq -r '.meta.githubCommitAuthorName // .creator.username // "unknown"' <<<"$deployment") - deployment_link="https://vercel.com/kilocode/kilocode-app/${deployment_id#dpl_}" + deployment_link="https://vercel.com/kilocode/$project/${deployment_id#dpl_}" payload=$(jq -n \ --arg dataset "$AXIOM_DATASET" \ --arg time "$deployment_time" \ - --arg title "kilocode-app · $short_sha" \ + --arg title "$project · $short_sha" \ --arg description "$commit_message · $commit_ref · $commit_author" \ --arg url "$deployment_link" \ - '{datasets:[$dataset], type:"kilocode-app-production-deployment", time:$time, title:$title, description:$description, url:$url}') + --arg type "$project-production-deployment" \ + '{datasets:[$dataset], type:$type, time:$time, title:$title, description:$description, url:$url}') curl --fail-with-body --silent --show-error \ --request POST \