From aa9114cbff50592f9a0944dcd734b37a748cff1f Mon Sep 17 00:00:00 2001 From: Manas Srivastava Date: Sat, 6 Jun 2026 04:31:55 +0530 Subject: [PATCH 1/4] ci(nr): emit CI test-run results to New Relic (Wave 5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wave 5 of the CI integration plan (docs/ci/01-CI-INTEGRATION-DESIGN.md §NR observability): push CI test/gate/deploy results to New Relic so ANY red run is studyable from an NR dashboard, not just the GitHub Actions log. Adds a reusable composite action .github/actions/nr-ci-event that POSTs an InstantCITestRun custom event on every gated job (always) plus an InstantCITestFailure event on failure, via the NR Event API (insights-collector .../v1/accounts//events) authenticated with the ingest license key. Wired into build-and-test (ci.yml), coverage (coverage.yml), and the deploy gate (deploy.yml) as an `if: always()` step. No-op contract: when NEW_RELIC_LICENSE_KEY or NEW_RELIC_ACCOUNT_ID is absent (fork PRs, unprovisioned repo) the action prints the payload it WOULD send and exits 0 — observability never reds a PR. Free-form values flow through env, not shell interpolation (injection-safe). Additive only; gates unchanged. Schema InstantCITestRun{repo,workflow,branch,commit_sha,pr_number,result, duration_ms,suite,...}; InstantCITestFailure{...,failed_step,log_url}. NR dashboard + alerts land in the infra repo (instanode-ci-health). Operator action: provision NEW_RELIC_LICENSE_KEY + NEW_RELIC_ACCOUNT_ID as GitHub Actions secrets on the api repo (license key = same k8s instant-secrets value; account id per infra/newrelic/README.md). Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/nr-ci-event/action.yml | 193 +++++++++++++++++++++++++ .github/workflows/ci.yml | 16 ++ .github/workflows/coverage.yml | 15 ++ .github/workflows/deploy.yml | 15 ++ 4 files changed, 239 insertions(+) create mode 100644 .github/actions/nr-ci-event/action.yml diff --git a/.github/actions/nr-ci-event/action.yml b/.github/actions/nr-ci-event/action.yml new file mode 100644 index 00000000..a17c2a33 --- /dev/null +++ b/.github/actions/nr-ci-event/action.yml @@ -0,0 +1,193 @@ +name: nr-ci-event +description: >- + Emit a CI test-run result to New Relic so a red CI run (test, e2e, smoke, + deploy gate) is studyable from NR dashboards, not just GitHub logs. Posts an + InstantCITestRun custom event on every gated job, plus an InstantCITestFailure + event when result=fail. No-ops cleanly (logs the payload it WOULD send) when + the NR secret/account is absent — never fails the calling job because NR is + unreachable (fork PRs, secret not yet provisioned). + +# Mechanism (CLAUDE.md design ref docs/ci/01-CI-INTEGRATION-DESIGN.md §NR +# observability): the NR Event API is a single HTTP POST to +# https://insights-collector.newrelic.com/v1/accounts//events +# authenticated with the ingest license key (the SAME NEW_RELIC_LICENSE_KEY the +# Go agents use at runtime — it is a valid Insert Key for the Event API). The +# account id is the numeric NEW_RELIC_ACCOUNT_ID. Both are passed as inputs from +# repo secrets by the caller. When EITHER is empty the action prints the payload +# and exits 0 (the no-op-without-secret contract — rule: never red a PR because +# NR is down). EU-region accounts override the collector host via nr-region. + +inputs: + # --- NR credentials (caller passes from secrets; empty => no-op) --- + license-key: + description: >- + NR ingest license key (Insert Key for the Event API). Pass + ${{ secrets.NEW_RELIC_LICENSE_KEY }}. Empty => action no-ops (dry-run log). + required: false + default: '' + account-id: + description: >- + Numeric NR account id. Pass ${{ secrets.NEW_RELIC_ACCOUNT_ID }}. + Empty => action no-ops (dry-run log). + required: false + default: '' + nr-region: + description: 'US (default) or EU — selects the insights-collector host.' + required: false + default: 'US' + + # --- event payload (caller fills from the GitHub context + job result) --- + result: + description: 'pass | fail — usually ${{ job.status == ''success'' && ''pass'' || ''fail'' }}.' + required: true + suite: + description: >- + Logical suite name, e.g. build-and-test, coverage, playwright, pr-smoke, + e2e-prod, deploy-gate. The dashboard FACETs on this. + required: true + repo: + description: 'Repository (owner/name). Default ${{ github.repository }}.' + required: false + default: '' + workflow: + description: 'Workflow name. Default ${{ github.workflow }}.' + required: false + default: '' + branch: + description: 'Branch ref name. Default ${{ github.ref_name }}.' + required: false + default: '' + commit-sha: + description: 'Commit SHA under test. Default ${{ github.sha }}.' + required: false + default: '' + pr-number: + description: 'PR number (empty on push). Default ${{ github.event.pull_request.number }}.' + required: false + default: '' + duration-ms: + description: 'Suite duration in milliseconds (0 if not measured).' + required: false + default: '0' + failed-step: + description: 'On failure: the step/phase that failed (free text, no PII). Empty on pass.' + required: false + default: '' + log-url: + description: >- + URL to the run logs for triage. Default the run's GitHub Actions URL. + required: false + default: '' + +runs: + using: composite + steps: + - name: Emit CI result to New Relic (no-op without secret) + shell: bash + env: + # All untrusted/free-form values flow through env, never interpolated + # into the shell body — same injection-safe posture as ci.yml's + # dispatch-auth-contract-e2e job. + NR_LICENSE_KEY: ${{ inputs.license-key }} + NR_ACCOUNT_ID: ${{ inputs.account-id }} + NR_REGION: ${{ inputs.nr-region }} + EV_RESULT: ${{ inputs.result }} + EV_SUITE: ${{ inputs.suite }} + EV_REPO: ${{ inputs.repo != '' && inputs.repo || github.repository }} + EV_WORKFLOW: ${{ inputs.workflow != '' && inputs.workflow || github.workflow }} + EV_BRANCH: ${{ inputs.branch != '' && inputs.branch || github.ref_name }} + EV_COMMIT: ${{ inputs.commit-sha != '' && inputs.commit-sha || github.sha }} + EV_PR: ${{ inputs.pr-number }} + EV_DURATION_MS: ${{ inputs.duration-ms }} + EV_FAILED_STEP: ${{ inputs.failed-step }} + EV_LOG_URL: ${{ inputs.log-url != '' && inputs.log-url || format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }} + EV_EVENT_NAME: ${{ github.event_name }} + EV_ACTOR: ${{ github.actor }} + run: | + set -uo pipefail + + # Normalise the result to the pass|fail enum the dashboard FACETs on. + # Anything that isn't exactly "pass" is treated as "fail" so a typo or + # a cancelled job reads as a non-pass (conservative — never a false green). + case "${EV_RESULT}" in + pass) RESULT="pass" ;; + *) RESULT="fail" ;; + esac + + # Build the InstantCITestRun event (always) and, on fail, the + # InstantCITestFailure event. jq composes the JSON so every value is + # passed as an argument (no shell concatenation of free-form text). + DURATION="${EV_DURATION_MS}" + case "${DURATION}" in ''|*[!0-9]*) DURATION=0 ;; esac + + RUN_EVENT=$(jq -n -c \ + --arg eventType "InstantCITestRun" \ + --arg repo "${EV_REPO}" \ + --arg workflow "${EV_WORKFLOW}" \ + --arg branch "${EV_BRANCH}" \ + --arg commit_sha "${EV_COMMIT}" \ + --arg pr_number "${EV_PR}" \ + --arg result "${RESULT}" \ + --arg suite "${EV_SUITE}" \ + --arg event_name "${EV_EVENT_NAME}" \ + --arg actor "${EV_ACTOR}" \ + --arg log_url "${EV_LOG_URL}" \ + --argjson duration_ms "${DURATION}" \ + '{eventType:$eventType, repo:$repo, workflow:$workflow, branch:$branch, + commit_sha:$commit_sha, pr_number:$pr_number, result:$result, + suite:$suite, event_name:$event_name, actor:$actor, log_url:$log_url, + duration_ms:$duration_ms}') + + PAYLOAD="[${RUN_EVENT}]" + if [ "${RESULT}" = "fail" ]; then + FAIL_EVENT=$(jq -n -c \ + --arg eventType "InstantCITestFailure" \ + --arg repo "${EV_REPO}" \ + --arg workflow "${EV_WORKFLOW}" \ + --arg branch "${EV_BRANCH}" \ + --arg commit_sha "${EV_COMMIT}" \ + --arg pr_number "${EV_PR}" \ + --arg suite "${EV_SUITE}" \ + --arg failed_step "${EV_FAILED_STEP}" \ + --arg log_url "${EV_LOG_URL}" \ + --arg event_name "${EV_EVENT_NAME}" \ + '{eventType:$eventType, repo:$repo, workflow:$workflow, branch:$branch, + commit_sha:$commit_sha, pr_number:$pr_number, suite:$suite, + failed_step:$failed_step, log_url:$log_url, event_name:$event_name}') + PAYLOAD="[${RUN_EVENT},${FAIL_EVENT}]" + fi + + # No-op-without-secret contract: print what WOULD be sent and exit 0 so + # a fork PR (no secret) or an unprovisioned repo never reds because NR + # is unreachable. + if [ -z "${NR_LICENSE_KEY}" ] || [ -z "${NR_ACCOUNT_ID}" ]; then + echo "::notice title=nr-ci-event::NEW_RELIC_LICENSE_KEY or NEW_RELIC_ACCOUNT_ID absent — dry-run only (no event sent)." + echo "would POST to NR Event API the following payload:" + echo "${PAYLOAD}" | jq . + exit 0 + fi + + case "$(echo "${NR_REGION}" | tr '[:lower:]' '[:upper:]')" in + EU) HOST="insights-collector.eu01.nr-data.net" ;; + *) HOST="insights-collector.newrelic.com" ;; + esac + URL="https://${HOST}/v1/accounts/${NR_ACCOUNT_ID}/events" + + echo "POSTing ${RESULT} result for suite='${EV_SUITE}' to NR account ${NR_ACCOUNT_ID} (${HOST})" + HTTP_CODE=$(curl -sS -o /tmp/nr_ci_event.out -w '%{http_code}' \ + -X POST "${URL}" \ + -H "Content-Type: application/json" \ + -H "Api-Key: ${NR_LICENSE_KEY}" \ + --data-binary "${PAYLOAD}" || echo "000") + + echo "NR Event API responded HTTP ${HTTP_CODE}" + cat /tmp/nr_ci_event.out 2>/dev/null || true + echo + + # NR returns 200 on accept. Any other code (incl. network failure 000) + # is logged as a warning but NEVER fails the job — observability must + # not gate the pipeline. + if [ "${HTTP_CODE}" != "200" ]; then + echo "::warning title=nr-ci-event::NR Event API returned ${HTTP_CODE} (expected 200). CI result not recorded in NR; not failing the job." + fi + exit 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75a02c2a..c945b252 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -179,6 +179,22 @@ jobs: # the BillingHandler.ensureRazorpayFns data race. - run: go test ./... -short -race -count=1 -p 1 + # Wave 5 — push the gated-test result to New Relic so a red run is + # studyable from an NR dashboard, not just the GitHub Actions log. + # if: always() so a FAILED `go test` step still records the failure + # (InstantCITestRun result=fail + InstantCITestFailure). No-ops cleanly + # when the NR secret/account is absent (fork PRs) — never reds the PR. + - name: Emit CI result to New Relic + if: always() + uses: ./.github/actions/nr-ci-event + with: + license-key: ${{ secrets.NEW_RELIC_LICENSE_KEY }} + account-id: ${{ secrets.NEW_RELIC_ACCOUNT_ID }} + result: ${{ job.status == 'success' && 'pass' || 'fail' }} + suite: build-and-test + pr-number: ${{ github.event.pull_request.number }} + failed-step: ${{ job.status != 'success' && 'go build / vet / test (-short -race -p 1)' || '' }} + # E2E requires a live Kubernetes stack (see repo CLAUDE.md). This job does not # run on push/PR — only on schedule or manual dispatch — so default CI stays fast. e2e: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 40117f1e..d87ed85c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -206,3 +206,18 @@ jobs: echo "Total project coverage: ${total}%" awk -v t="$total" 'BEGIN { exit (t+0 >= 95) ? 0 : 1 }' \ || { echo "::error::Production coverage ${total}% is below the 95% floor"; exit 1; } + + # Wave 5 — record the coverage-gate outcome in New Relic (suite=coverage) + # so a coverage red is visible alongside the test red on the CI-health + # dashboard. if: always() captures both the patch-gate and floor-gate + # failures above. No-ops without the NR secret. + - name: Emit coverage result to New Relic + if: always() + uses: ./.github/actions/nr-ci-event + with: + license-key: ${{ secrets.NEW_RELIC_LICENSE_KEY }} + account-id: ${{ secrets.NEW_RELIC_ACCOUNT_ID }} + result: ${{ job.status == 'success' && 'pass' || 'fail' }} + suite: coverage + pr-number: ${{ github.event.pull_request.number }} + failed-step: ${{ job.status != 'success' && 'coverage gate (100% patch / 95% floor)' || '' }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b9b3fe87..d4b7d3fd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -323,3 +323,18 @@ jobs: done echo "::error::live /healthz never reported commit_id=${SHORT_SHA}" exit 1 + + # Wave 5 — record the deploy outcome in New Relic (suite=deploy) so a + # failed deploy (test gate, image build, rollout, or the /healthz + # build-SHA gate above) is studyable from the CI-health dashboard, not + # only the Actions log. if: always() captures the failure path. + # No-ops without the NR secret. + - name: Emit deploy result to New Relic + if: always() + uses: ./.github/actions/nr-ci-event + with: + license-key: ${{ secrets.NEW_RELIC_LICENSE_KEY }} + account-id: ${{ secrets.NEW_RELIC_ACCOUNT_ID }} + result: ${{ job.status == 'success' && 'pass' || 'fail' }} + suite: deploy + failed-step: ${{ job.status != 'success' && 'deploy (test gate / build / rollout / healthz SHA gate)' || '' }} From 73465522205db471e45ffdcdda8d5189b6f5a6e0 Mon Sep 17 00:00:00 2001 From: Manas Srivastava Date: Sat, 6 Jun 2026 05:25:14 +0530 Subject: [PATCH 2/4] =?UTF-8?q?fix(ci):=20composite=20action=20cannot=20re?= =?UTF-8?q?ad=20github/secrets/job=20=E2=80=94=20caller=20passes=20them=20?= =?UTF-8?q?as=20inputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nr-ci-event composite action referenced github.*/secrets.*/job.status in its own env: block; GitHub rejects those contexts inside a composite action (TemplateValidationException 'Unrecognized named-value'). Move all resolution to the caller's with: block (which CAN read those contexts) and have the action read only inputs.*. Adds event-name + actor inputs. Callers now pass repo/workflow/ branch/commit-sha/log-url/event-name/actor from the github context. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/nr-ci-event/action.yml | 48 +++++++++++++++++--------- .github/workflows/ci.yml | 7 ++++ .github/workflows/coverage.yml | 7 ++++ .github/workflows/deploy.yml | 7 ++++ 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/.github/actions/nr-ci-event/action.yml b/.github/actions/nr-ci-event/action.yml index a17c2a33..68de13ee 100644 --- a/.github/actions/nr-ci-event/action.yml +++ b/.github/actions/nr-ci-event/action.yml @@ -45,24 +45,28 @@ inputs: Logical suite name, e.g. build-and-test, coverage, playwright, pr-smoke, e2e-prod, deploy-gate. The dashboard FACETs on this. required: true + # NOTE: composite actions cannot read the `github` context in their own + # expressions, so the caller MUST pass these from its `with:` block (e.g. + # repo: ${{ github.repository }}). The defaults below only apply when a caller + # omits them entirely. repo: - description: 'Repository (owner/name). Default ${{ github.repository }}.' + description: 'Repository (owner/name). Caller passes ${{ github.repository }}.' required: false default: '' workflow: - description: 'Workflow name. Default ${{ github.workflow }}.' + description: 'Workflow name. Caller passes ${{ github.workflow }}.' required: false default: '' branch: - description: 'Branch ref name. Default ${{ github.ref_name }}.' + description: 'Branch ref name. Caller passes ${{ github.ref_name }}.' required: false default: '' commit-sha: - description: 'Commit SHA under test. Default ${{ github.sha }}.' + description: 'Commit SHA under test. Caller passes ${{ github.sha }}.' required: false default: '' pr-number: - description: 'PR number (empty on push). Default ${{ github.event.pull_request.number }}.' + description: 'PR number (empty on push). Caller passes ${{ github.event.pull_request.number }}.' required: false default: '' duration-ms: @@ -74,8 +78,15 @@ inputs: required: false default: '' log-url: - description: >- - URL to the run logs for triage. Default the run's GitHub Actions URL. + description: 'URL to the run logs for triage. Caller passes the run URL.' + required: false + default: '' + event-name: + description: 'GitHub event name. Caller passes ${{ github.event_name }}.' + required: false + default: '' + actor: + description: 'GitHub actor. Caller passes ${{ github.actor }}.' required: false default: '' @@ -85,24 +96,27 @@ runs: - name: Emit CI result to New Relic (no-op without secret) shell: bash env: - # All untrusted/free-form values flow through env, never interpolated - # into the shell body — same injection-safe posture as ci.yml's - # dispatch-auth-contract-e2e job. + # Composite actions may reference ONLY `inputs` (+ env/runner/steps) in + # their expressions — `secrets`, `job`, and `github` are NOT available + # here, so the CALLER resolves those and passes them as inputs. All + # untrusted/free-form values flow through env, never interpolated into + # the shell body (injection-safe — same posture as ci.yml's + # dispatch-auth-contract-e2e job). NR_LICENSE_KEY: ${{ inputs.license-key }} NR_ACCOUNT_ID: ${{ inputs.account-id }} NR_REGION: ${{ inputs.nr-region }} EV_RESULT: ${{ inputs.result }} EV_SUITE: ${{ inputs.suite }} - EV_REPO: ${{ inputs.repo != '' && inputs.repo || github.repository }} - EV_WORKFLOW: ${{ inputs.workflow != '' && inputs.workflow || github.workflow }} - EV_BRANCH: ${{ inputs.branch != '' && inputs.branch || github.ref_name }} - EV_COMMIT: ${{ inputs.commit-sha != '' && inputs.commit-sha || github.sha }} + EV_REPO: ${{ inputs.repo }} + EV_WORKFLOW: ${{ inputs.workflow }} + EV_BRANCH: ${{ inputs.branch }} + EV_COMMIT: ${{ inputs.commit-sha }} EV_PR: ${{ inputs.pr-number }} EV_DURATION_MS: ${{ inputs.duration-ms }} EV_FAILED_STEP: ${{ inputs.failed-step }} - EV_LOG_URL: ${{ inputs.log-url != '' && inputs.log-url || format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }} - EV_EVENT_NAME: ${{ github.event_name }} - EV_ACTOR: ${{ github.actor }} + EV_LOG_URL: ${{ inputs.log-url }} + EV_EVENT_NAME: ${{ inputs.event-name }} + EV_ACTOR: ${{ inputs.actor }} run: | set -uo pipefail diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c945b252..791490fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -194,6 +194,13 @@ jobs: suite: build-and-test pr-number: ${{ github.event.pull_request.number }} failed-step: ${{ job.status != 'success' && 'go build / vet / test (-short -race -p 1)' || '' }} + repo: ${{ github.repository }} + workflow: ${{ github.workflow }} + branch: ${{ github.ref_name }} + commit-sha: ${{ github.sha }} + log-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + event-name: ${{ github.event_name }} + actor: ${{ github.actor }} # E2E requires a live Kubernetes stack (see repo CLAUDE.md). This job does not # run on push/PR — only on schedule or manual dispatch — so default CI stays fast. diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d87ed85c..db354bf8 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -221,3 +221,10 @@ jobs: suite: coverage pr-number: ${{ github.event.pull_request.number }} failed-step: ${{ job.status != 'success' && 'coverage gate (100% patch / 95% floor)' || '' }} + repo: ${{ github.repository }} + workflow: ${{ github.workflow }} + branch: ${{ github.ref_name }} + commit-sha: ${{ github.sha }} + log-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + event-name: ${{ github.event_name }} + actor: ${{ github.actor }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d4b7d3fd..0b883beb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -338,3 +338,10 @@ jobs: result: ${{ job.status == 'success' && 'pass' || 'fail' }} suite: deploy failed-step: ${{ job.status != 'success' && 'deploy (test gate / build / rollout / healthz SHA gate)' || '' }} + repo: ${{ github.repository }} + workflow: ${{ github.workflow }} + branch: ${{ github.ref_name }} + commit-sha: ${{ github.sha }} + log-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + event-name: ${{ github.event_name }} + actor: ${{ github.actor }} From 46220f5ee240bc4e2396e4a34bf39e3633042f68 Mon Sep 17 00:00:00 2001 From: Manas Srivastava Date: Sat, 6 Jun 2026 05:28:59 +0530 Subject: [PATCH 3/4] fix(ci): strip ${{ }} from composite-action input descriptions GitHub evaluates ${{ }} even inside input description: strings; the example '${{ secrets... }}' text triggered Unrecognized-named-value. Plain text now; only runs: keeps inputs.* expressions. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/nr-ci-event/action.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/actions/nr-ci-event/action.yml b/.github/actions/nr-ci-event/action.yml index 68de13ee..4da401f4 100644 --- a/.github/actions/nr-ci-event/action.yml +++ b/.github/actions/nr-ci-event/action.yml @@ -22,12 +22,12 @@ inputs: license-key: description: >- NR ingest license key (Insert Key for the Event API). Pass - ${{ secrets.NEW_RELIC_LICENSE_KEY }}. Empty => action no-ops (dry-run log). + `secrets.NEW_RELIC_LICENSE_KEY`. Empty => action no-ops (dry-run log). required: false default: '' account-id: description: >- - Numeric NR account id. Pass ${{ secrets.NEW_RELIC_ACCOUNT_ID }}. + Numeric NR account id. Pass `secrets.NEW_RELIC_ACCOUNT_ID`. Empty => action no-ops (dry-run log). required: false default: '' @@ -38,7 +38,7 @@ inputs: # --- event payload (caller fills from the GitHub context + job result) --- result: - description: 'pass | fail — usually ${{ job.status == ''success'' && ''pass'' || ''fail'' }}.' + description: 'pass | fail — usually `job.status == ''success'' && ''pass'' || ''fail''`.' required: true suite: description: >- @@ -47,26 +47,26 @@ inputs: required: true # NOTE: composite actions cannot read the `github` context in their own # expressions, so the caller MUST pass these from its `with:` block (e.g. - # repo: ${{ github.repository }}). The defaults below only apply when a caller + # repo: `github.repository`). The defaults below only apply when a caller # omits them entirely. repo: - description: 'Repository (owner/name). Caller passes ${{ github.repository }}.' + description: 'Repository (owner/name). Caller passes `github.repository`.' required: false default: '' workflow: - description: 'Workflow name. Caller passes ${{ github.workflow }}.' + description: 'Workflow name. Caller passes `github.workflow`.' required: false default: '' branch: - description: 'Branch ref name. Caller passes ${{ github.ref_name }}.' + description: 'Branch ref name. Caller passes `github.ref_name`.' required: false default: '' commit-sha: - description: 'Commit SHA under test. Caller passes ${{ github.sha }}.' + description: 'Commit SHA under test. Caller passes `github.sha`.' required: false default: '' pr-number: - description: 'PR number (empty on push). Caller passes ${{ github.event.pull_request.number }}.' + description: 'PR number (empty on push). Caller passes `github.event.pull_request.number`.' required: false default: '' duration-ms: @@ -82,11 +82,11 @@ inputs: required: false default: '' event-name: - description: 'GitHub event name. Caller passes ${{ github.event_name }}.' + description: 'GitHub event name. Caller passes `github.event_name`.' required: false default: '' actor: - description: 'GitHub actor. Caller passes ${{ github.actor }}.' + description: 'GitHub actor. Caller passes `github.actor`.' required: false default: '' From a08befd8ac8752127399474418bb7273ae5debe1 Mon Sep 17 00:00:00 2001 From: Manas Srivastava Date: Sat, 6 Jun 2026 05:44:22 +0530 Subject: [PATCH 4/4] fix(ci): point nr-ci-event at ./api/... in coverage+deploy (nested checkout) coverage.yml and deploy.yml check the api repo out into ./api (path: api), so a local-action 'uses: ./.github/actions/nr-ci-event' resolves to the workspace root (empty) and 404s ('Can't find action.yml'). Reference the action at its real nested path ./api/.github/actions/nr-ci-event in those two workflows. ci.yml checks out at the root, so it keeps ./.github/actions/nr-ci-event. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/coverage.yml | 2 +- .github/workflows/deploy.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index db354bf8..e24fabdd 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -213,7 +213,7 @@ jobs: # failures above. No-ops without the NR secret. - name: Emit coverage result to New Relic if: always() - uses: ./.github/actions/nr-ci-event + uses: ./api/.github/actions/nr-ci-event with: license-key: ${{ secrets.NEW_RELIC_LICENSE_KEY }} account-id: ${{ secrets.NEW_RELIC_ACCOUNT_ID }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0b883beb..2c1c0d6f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -331,7 +331,7 @@ jobs: # No-ops without the NR secret. - name: Emit deploy result to New Relic if: always() - uses: ./.github/actions/nr-ci-event + uses: ./api/.github/actions/nr-ci-event with: license-key: ${{ secrets.NEW_RELIC_LICENSE_KEY }} account-id: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}