From ea45a6406fe23942f499782061d4cd841a495493 Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Sun, 26 Jul 2026 17:30:36 -0700 Subject: [PATCH 01/10] ci(bots): add daily retrospective (rolling learning PR) + bump engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the daily learning-extraction flow (was not enabled on this repo) on the current engine d05dcb1, which maintains ONE rolling learning PR on a stable branch (ai/learning-pr). - engineer-bot-learning.yml: daily cron + workflow_dispatch; PAT-free via the existing bot-prelude; actions:read for Track B (author-run mining). - .bot/config.yaml: add retrospective: block (engine-owned batch prompt, no override) and author.knowledge_log to close the loop (author reads what the retrospective writes). - Seed .claude/knowledge/learning-log.md. - Bump bot-prelude engine-ref default 5368de7 → d05dcb1. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- .bot/config.yaml | 21 ++++ .claude/knowledge/learning-log.md | 11 ++ .github/actions/bot-prelude/action.yml | 2 +- .github/workflows/engineer-bot-learning.yml | 124 ++++++++++++++++++++ 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 .claude/knowledge/learning-log.md create mode 100644 .github/workflows/engineer-bot-learning.yml diff --git a/.bot/config.yaml b/.bot/config.yaml index dab384f3f..3450f8715 100644 --- a/.bot/config.yaml +++ b/.bot/config.yaml @@ -86,6 +86,10 @@ author: issue_url: ISSUE_URL context_files: - issue_body.txt # {{issue_body}} + # Close the loop: the author phase READS this log (newest-first, capped) into its + # system prompt as accumulated lessons, so retrospective learnings actually steer + # future fixes. MUST match retrospective.log_path below (write half ↔ read half). + knowledge_log: .claude/knowledge/learning-log.md # Engine orchestration for the author phase. `bug-fix` runs the plan → # author_tests → fix pipeline (write a failing test → fix the code → re-run to @@ -94,3 +98,20 @@ author: # testing specifics (commands, layout, fixtures) live in prompts/engineer/ # system.md, not here. flow: bug-fix + +# Daily-cron learning extraction (retrospective flow). Run by engineer-bot-learning.yml +# via `python -m databricks_bot_engine.engineer_bot.retrospective`: over an adaptive +# look-back window the engine ITSELF gathers merged PRs (diff + review comments) AND +# recent engineer-bot author-run logs, and if the model finds durable learnings opens +# ONE rolling PR appending a dated section to log_path. Human-gated — never commits the +# canonical log directly. Omitting this block makes the retrospective a no-op. +# +# No `system_prompt` override: the engine ships the authoritative batch-aware base +# (engineer_prompts.RETRO_SYSTEM_PROMPT); a `.bot/prompts/` override is optional. +# No `context_files` — the daily-cron engine enumerates its own sources via the +# GitHub API (context_files is an AUTHOR-phase key only). +retrospective: + log_path: .claude/knowledge/learning-log.md # MUST match author.knowledge_log above + branch_prefix: ai/learning-pr- # rolling PR lands on the derived stable branch `ai/learning-pr` + pr_label: engineer-bot-learning + author_workflow: engineer-bot.yml # Track B: which workflow's author runs to mine diff --git a/.claude/knowledge/learning-log.md b/.claude/knowledge/learning-log.md new file mode 100644 index 000000000..0e0c2f2e9 --- /dev/null +++ b/.claude/knowledge/learning-log.md @@ -0,0 +1,11 @@ +# Engineer-bot learning log + +Durable, reusable engineering learnings distilled by the daily retrospective +(`engineer-bot-learning.yml` → `databricks_bot_engine.engineer_bot.retrospective`). +Each daily run appends one dated section of 0..N learnings to a single rolling PR +until a human merges it. The engineer author phase reads this log (see +`.bot/config.yaml` `author.knowledge_log`) so past lessons steer future fixes. + +## Entries + +--- *Add new entries above this line (oldest→newest); newest sections sort to the bottom.* --- diff --git a/.github/actions/bot-prelude/action.yml b/.github/actions/bot-prelude/action.yml index b2c0a112d..aa825648e 100644 --- a/.github/actions/bot-prelude/action.yml +++ b/.github/actions/bot-prelude/action.yml @@ -31,7 +31,7 @@ inputs: # value to move every bot to a new engine commit; never @main. description: 'Engine commit SHA (full 40-char) to install.' required: false - default: '5368de773858bef9b2a8bbf3099d08a4132a8487' + default: 'd05dcb113332401b4aee8d6aa05c7107399ad44f' engine-repo: description: 'owner/name of the engine repo.' required: false diff --git a/.github/workflows/engineer-bot-learning.yml b/.github/workflows/engineer-bot-learning.yml new file mode 100644 index 000000000..33a3e5847 --- /dev/null +++ b/.github/workflows/engineer-bot-learning.yml @@ -0,0 +1,124 @@ +# Engineer Bot — learning (retrospective) extraction — DAILY CRON. +# +# Over an adaptive look-back window the engine gathers merged PRs (diff + review +# comments) AND recent engineer-bot author-run console logs ITSELF via the GitHub +# API — no in-workflow context gathering, no per-PR trigger — and if the model +# finds durable, reusable learnings, opens ONE ROLLING PR on a stable branch +# (`ai/learning-pr`), appending a dated section per day until a human merges it. +# Human-gated by design: it NEVER commits the canonical log directly. +# +# Own job (NOT `uses: databricks/databricks-bot-engine/...`): an external repo +# can't resolve the internal engine's reusable workflows ("not found"). It shares +# the SAME prelude the other bots use — ./.github/actions/bot-prelude (tokens + +# Node + pinned engine install) — so the engine pin stays single-sourced in +# bot-prelude's `engine-ref` default (no second SHA to drift). +# +# Opt-in is purely via the `retrospective:` block in .bot/config.yaml + this +# workflow; absent that block the engine phase is a clean no-op. +name: Engineer Bot — Learning + +on: + schedule: + # 17:23 UTC daily — off-peak, off-:00 minute (GitHub delays/drops on-the-hour crons). + - cron: "23 17 * * *" + workflow_dispatch: + inputs: + since: + description: 'ISO lower bound to bound (shorten) the window and recover a wedged flow. Empty = adaptive cursor.' + type: string + default: '' + window-hours: + # STRING, not number: a `type: number` workflow_dispatch input fails the + # whole run at startup ("workflow file issue") when combined with the + # `schedule` trigger. argparse coerces it to int downstream. + description: 'Fallback look-back window (hours) used only when there is no prior successful run.' + type: string + default: '24' + +permissions: + contents: write # push the learning branch / open the learning PR + pull-requests: write + actions: read # Track B lists engineer-bot author runs + logs via the App token; + # the engineer-bot App installation must ALSO carry actions:read + # (a missing scope surfaces as a 403 that fails the whole run — + # list_author_runs raises, no escape hatch). + id-token: write # JFrog OIDC exchange for the engine/SDK/CLI install + +concurrency: + # One learning run at a time; a queued run waits rather than racing the rolling + # PR's branch. Not keyed on a PR number (this is a cron, no PR event). + group: engineer-bot-learning-cron + cancel-in-progress: false + +jobs: + learning: + environment: azure-prod # DATABRICKS_HOST / DATABRICKS_TOKEN live here + runs-on: + group: databricks-protected-runner-group + labels: [linux-ubuntu-latest] + timeout-minutes: 20 + steps: + # Checkout the default branch (the learning PR is cut from it) FIRST, so the + # local `./` composites below resolve. persist-credentials:false — the + # retrospective sets its own authenticated push remote (see the run step), + # so no token is left in .git/config. + - name: Checkout default branch (learning PR is cut from it) + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + persist-credentials: false + + # Route pip through the JFrog proxy for the engine install (the retrospective + # reads diffs via the API; it doesn't build/run the connector, so no poetry + # deps needed — just a Python interpreter + the engine). MUST run before + # bot-prelude (install-bot-engine reuses its PIP_INDEX_URL / JFROG_ACCESS_TOKEN). + - name: Set up JFrog (pip) + uses: ./.github/actions/setup-jfrog + with: + uv: "true" + + - name: Setup Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: '3.11' + + # Shared prelude: mint the engineer-bot token (opens the learning PR) + the + # engine-scoped token, set up Node, install the pinned engine (PAT-free). The + # engine pin comes from bot-prelude's `engine-ref` default — the SINGLE source + # of truth for every bot; there is no second SHA in this file to drift. + - name: Bot prelude (tokens + Node + engine install) + id: prelude + uses: ./.github/actions/bot-prelude + with: + app-id: ${{ secrets.ENGINEER_BOT_APP_ID }} + private-key: ${{ secrets.ENGINEER_BOT_APP_PRIVATE_KEY }} + + # NOTE: no git-identity step — the engine's retrospective configures the git + # user AND DCO sign-off itself from .bot/config.yaml `bot_login_prefix`. + # NOTE: no context-gather step — the daily-cron engine enumerates merged PRs + # + author runs itself over the adaptive window. + - name: Extract learnings + open rolling PR + env: + GH_TOKEN: ${{ steps.prelude.outputs.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + # Only the `/serving-endpoints/` prefix matters: + # sdk_agent.translate_endpoint rewrites this to `.../serving-endpoints/anthropic` + # and discards the model path segment. The effective model comes from + # .bot/config.yaml `retrospective.model` (or the engine default). + MODEL_ENDPOINT: https://${{ secrets.DATABRICKS_HOST }}/serving-endpoints/anthropic/invocations + DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }} + RUNNER_TEMP: ${{ runner.temp }} + SINCE: ${{ inputs.since }} + WINDOW_HOURS: ${{ inputs.window-hours }} + # The retrospective pushes the learning branch with a plain `git push + # origin`, and the checkout ran persist-credentials:false — so set an + # authenticated push remote from the minted App token first, mirroring + # engineer-bot.yml's publish step. --since / --window-hours are passed only + # when provided via workflow_dispatch (the schedule trigger leaves them + # empty → the adaptive cursor drives the window). + run: | + git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + args=(--repo-dir "$GITHUB_WORKSPACE") + [ -n "$SINCE" ] && args+=(--since "$SINCE") + [ -n "$WINDOW_HOURS" ] && args+=(--window-hours "$WINDOW_HOURS") + python -m databricks_bot_engine.engineer_bot.retrospective "${args[@]}" From a440a7b001db064f493cecadcdf80ce16b9a2734 Mon Sep 17 00:00:00 2001 From: "peco-engineer-bot[bot]" Date: Mon, 27 Jul 2026 02:45:13 +0000 Subject: [PATCH 02/10] ai: apply changes for #882 (1 review thread) Addresses: - #3653811351 at .github/workflows/engineer-bot-learning.yml:78 Signed-off-by: peco-engineer-bot[bot] --- .github/workflows/engineer-bot-learning.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/engineer-bot-learning.yml b/.github/workflows/engineer-bot-learning.yml index 33a3e5847..51e47d880 100644 --- a/.github/workflows/engineer-bot-learning.yml +++ b/.github/workflows/engineer-bot-learning.yml @@ -74,8 +74,6 @@ jobs: # bot-prelude (install-bot-engine reuses its PIP_INDEX_URL / JFROG_ACCESS_TOKEN). - name: Set up JFrog (pip) uses: ./.github/actions/setup-jfrog - with: - uv: "true" - name: Setup Python uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 From a3020af76062d37973c1a23425fdaaf01a5f9081 Mon Sep 17 00:00:00 2001 From: "peco-engineer-bot[bot]" Date: Mon, 27 Jul 2026 02:53:02 +0000 Subject: [PATCH 03/10] ai: apply changes for #882 (1 review thread) Addresses: - #3654123624 at .github/workflows/engineer-bot-learning.yml:75 Signed-off-by: peco-engineer-bot[bot] --- .github/workflows/engineer-bot-learning.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/engineer-bot-learning.yml b/.github/workflows/engineer-bot-learning.yml index 51e47d880..df9054b2b 100644 --- a/.github/workflows/engineer-bot-learning.yml +++ b/.github/workflows/engineer-bot-learning.yml @@ -68,13 +68,13 @@ jobs: fetch-depth: 0 persist-credentials: false - # Route pip through the JFrog proxy for the engine install (the retrospective - # reads diffs via the API; it doesn't build/run the connector, so no poetry - # deps needed — just a Python interpreter + the engine). MUST run before - # bot-prelude (install-bot-engine reuses its PIP_INDEX_URL / JFROG_ACCESS_TOKEN). - - name: Set up JFrog (pip) - uses: ./.github/actions/setup-jfrog - + # No JFrog setup step here: install-bot-engine (via bot-prelude) does its own + # keyless OIDC→JFrog mint and passes the credential through job-local files + + # per-command --index-url flags. It deliberately never reads PIP_INDEX_URL / + # JFROG_ACCESS_TOKEN from the environment, so a setup-jfrog step would be both + # redundant AND a credential leak (setup-jfrog exports a token-bearing + # PIP_INDEX_URL to $GITHUB_ENV, exposing it to every later step — including the + # one that runs the model). Mirrors the read-only sibling reviewer-bot.yml. - name: Setup Python uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: From 88f50aa11234c05b113fd9e8c3c501d4a2e153cb Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Sun, 26 Jul 2026 20:36:01 -0700 Subject: [PATCH 04/10] ci: post Python Integration Tests check as the driver-test app (fix app-pinned gate) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The required 'Python Integration Tests' check is pinned in the main ruleset to the driver-test app (driver-integration-test, integration_id 2731531). But the PR-side placeholder was posted via github.token, so it landed as github-actions — a DIFFERENT check context that does NOT satisfy the app-pinned gate. Result: the required check sat unfulfilled and PRs stayed BLOCKED even though a green 'Python Integration Tests' appeared in the list (e.g. #882). Mirror databricks-sql-go #426 / databricks-sql-nodejs #459: - Add skip-checks-reporter.yml, triggered by workflow_run on 'Trigger Integration Tests'. It runs in the base-repo context with secret access even for fork-triggered runs, mints the INTEGRATION_TEST_APP token, and posts 'Python Integration Tests'=success on every PR head — forks included — as the driver-test app, so the app-pinned gate is satisfied and every PR auto-enqueues. - Remove the inline skip-integration-tests-pr github.token stub (superseded; it posted under the wrong app and 403'd on forks entirely). - In merge-queue-python, mint the app token unconditionally and post the auto-pass (no-driver-change) and dispatch-failure checks with IT — the merge_group path isn't covered by the reporter, and a github.token check there likewise can't satisfy the pinned gate (queue would hang). The real result was already posted back by driver-test as the app; this aligns the no-op paths. The labeled-PR preview's inline checks stay on github.token (cosmetic; the reporter owns the pinned gate on pull_request events), matching go/nodejs. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- .github/workflows/skip-checks-reporter.yml | 74 ++++++++++++++ .../workflows/trigger-integration-tests.yml | 99 +++++++------------ 2 files changed, 108 insertions(+), 65 deletions(-) create mode 100644 .github/workflows/skip-checks-reporter.yml diff --git a/.github/workflows/skip-checks-reporter.yml b/.github/workflows/skip-checks-reporter.yml new file mode 100644 index 000000000..fcb8f7457 --- /dev/null +++ b/.github/workflows/skip-checks-reporter.yml @@ -0,0 +1,74 @@ +name: Report Integration Test Skip + +# Posts the PR-open "skipped" placeholder for the required +# `Python Integration Tests` check, as the driver-test GitHub App. +# +# Why a separate workflow (and not an inline job in trigger-integration-tests.yml): +# - The branch ruleset pins the required `Python Integration Tests` check to the +# driver-test app's integration id (driver-integration-test). Only a check +# posted BY that app satisfies the gate — a `github.token` (github-actions) +# check of the same name does NOT (it is a different check context). +# - A PR from a fork runs its `pull_request` workflows with a READ-ONLY GITHUB_TOKEN +# and no access to secrets, so it cannot mint the app token and cannot post any +# check on its own head. That would leave fork PRs unable to enter the merge queue +# without a maintainer label. +# - `workflow_run` workflows always execute in THIS (base) repo's context using the +# workflow definition from the default branch, with full secret access — even when +# the run that triggered them came from a fork. That lets us post the app-attributed +# placeholder on any PR head, fork or not, so every PR can auto-enqueue. +# +# SECURITY: this workflow runs with secrets in a privileged context. It MUST NOT check +# out or execute any PR/fork-controlled content. It only calls checks.create with a +# static body; the sole fork-controlled input is `workflow_run.head_sha`, an opaque +# commit SHA passed to the API. Do not add `actions/checkout` or a `run:` step that +# executes repo content here. +# +# The real integration suite is unaffected: it runs as the required gate on the +# `merge_group` commit (and as a label preview on internal PRs), dispatched by +# trigger-integration-tests.yml. This workflow only posts the pre-merge placeholder. +# Mirrors databricks-sql-go / databricks-sql-nodejs. + +on: + workflow_run: + workflows: ["Trigger Integration Tests"] + types: [requested] + +jobs: + report-skip: + # Only for PR-triggered runs; the merge_group run posts the real required check. + if: github.event.workflow_run.event == 'pull_request' + runs-on: + group: databricks-protected-runner-group + labels: linux-ubuntu-latest + permissions: + checks: write + steps: + - name: Generate GitHub App token (this repo) + id: app-token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + with: + app-id: ${{ secrets.INTEGRATION_TEST_APP_ID }} + private-key: ${{ secrets.INTEGRATION_TEST_PRIVATE_KEY }} + owner: databricks + repositories: databricks-sql-python + + - name: Post skipped Python Integration Tests check + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + env: + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'Python Integration Tests', + head_sha: process.env.HEAD_SHA, + status: 'completed', + conclusion: 'success', + completed_at: new Date().toISOString(), + output: { + title: 'Skipped on PR — runs in merge queue', + summary: 'Python Integration Tests are skipped on ordinary PR events and run as the required gate in the merge queue (dispatched to databricks-driver-test). Add the `integration-test` label to preview them on this PR. (Label previews cannot run on fork PRs, which lack secret access; fork PRs are exercised by the required merge-queue run.)', + }, + }); diff --git a/.github/workflows/trigger-integration-tests.yml b/.github/workflows/trigger-integration-tests.yml index b8bb90ba2..f717964b8 100644 --- a/.github/workflows/trigger-integration-tests.yml +++ b/.github/workflows/trigger-integration-tests.yml @@ -116,55 +116,16 @@ jobs: body: body }); - # ============================================================================= - # For PRs: Always pass the Python Integration Tests check on - # non-label events. The real run happens in the merge queue (or via - # explicit label preview). Without this, the required - # `Python Integration Tests` check would block every PR that doesn't - # bother labelling. - # ============================================================================= - skip-integration-tests-pr: - if: github.event_name == 'pull_request' && github.event.action != 'labeled' && github.event.action != 'closed' - runs-on: - group: databricks-protected-runner-group - labels: linux-ubuntu-latest - permissions: - checks: write - steps: - - name: Skip Python Integration Tests - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 - with: - github-token: ${{ github.token }} - script: | - // On FORK PRs GitHub forces GITHUB_TOKEN to read-only regardless of - // the declared `checks: write`, so checks.create 403s ("Resource - // not accessible by integration"). Expected — a fork can't post - // check-runs on the base repo. Swallow the 403 for forks so this - // poster doesn't show a spurious failure; the real Python - // Integration Tests required check is posted by the merge_group run - // (full perms) when a maintainer queues the PR. Other errors fail loudly. - const isFork = context.payload.pull_request.head.repo.fork; - try { - await github.rest.checks.create({ - owner: context.repo.owner, - repo: context.repo.repo, - name: 'Python Integration Tests', - head_sha: context.payload.pull_request.head.sha, - status: 'completed', - conclusion: 'success', - completed_at: new Date().toISOString(), - output: { - title: 'Skipped on PR — runs in merge queue', - summary: 'Python Integration Tests are skipped on PRs and run as a required gate in the merge queue. Add the `integration-test` label to preview them on this PR.' - } - }); - } catch (e) { - if (isFork && e.status === 403) { - core.notice('Fork PR: cannot post the Python Integration Tests check-run (read-only token). It will be posted by the merge queue at merge time.'); - } else { - throw e; - } - } + # NOTE: the PR-open "skipped" placeholder for the required + # `Python Integration Tests` check is NOT posted here. It is posted by the + # companion workflow `skip-checks-reporter.yml`, which runs on `workflow_run` + # in the base-repo context so it can post the check as the driver-test app — + # the app the branch ruleset PINS the required check to — on EVERY PR head, + # including fork PRs (whose own `pull_request` run gets a read-only token and + # cannot post checks at all). A `pull_request`-triggered `github.token` stub + # here posted the check as `github-actions`, which is a DIFFERENT check + # context and did NOT satisfy the app-pinned gate — so the required check sat + # unfulfilled and PRs stayed blocked. See skip-checks-reporter.yml. # ============================================================================= # For PRs: Dispatch real tests when integration-test label is added. @@ -333,6 +294,23 @@ jobs: with: fetch-depth: 0 + # Mint the driver-test App token UNCONDITIONALLY. The required + # `Python Integration Tests` check is pinned in the ruleset to this app's + # integration id, so EVERY check this job posts — the real result (posted + # back by driver-test on dispatch), the no-op auto-pass, and the + # dispatch-failure — must be attributed to the same app, or it lands on a + # different check context and never satisfies the pinned gate (leaving the + # merge queue stuck). merge_group runs on the base repo with full secret + # access, so the mint always succeeds here. + - name: Generate GitHub App Token (internal repo) + id: app-token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + with: + app-id: ${{ secrets.INTEGRATION_TEST_APP_ID }} + private-key: ${{ secrets.INTEGRATION_TEST_PRIVATE_KEY }} + owner: databricks + repositories: databricks-driver-test + - name: Check if driver files changed id: changed env: @@ -352,9 +330,9 @@ jobs: if: steps.changed.outputs.changed != 'true' uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: - # Default workflow token — see the trigger-tests-pr job's - # equivalent step above for the rationale. - github-token: ${{ github.token }} + # App token, not github.token — the pinned required check is only + # satisfied by a check posted BY the driver-test app. + github-token: ${{ steps.app-token.outputs.token }} script: | await github.rest.checks.create({ owner: context.repo.owner, @@ -386,16 +364,6 @@ jobs: exit 1 fi - - name: Generate GitHub App Token (internal repo) - if: steps.changed.outputs.changed == 'true' - id: app-token - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 - with: - app-id: ${{ secrets.INTEGRATION_TEST_APP_ID }} - private-key: ${{ secrets.INTEGRATION_TEST_PRIVATE_KEY }} - owner: databricks - repositories: databricks-driver-test - - name: Dispatch Python tests if: steps.changed.outputs.changed == 'true' uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 @@ -418,9 +386,10 @@ jobs: if: failure() && steps.changed.outputs.changed == 'true' uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: - # Use the default workflow token, not the App token — see - # the rationale in the trigger-tests-pr job above. - github-token: ${{ github.token }} + # App token, not github.token — a github-actions failure check lands on + # a different context than the pinned gate, leaving the required check + # pending until the queue times out instead of failing fast. + github-token: ${{ steps.app-token.outputs.token }} script: | await github.rest.checks.create({ owner: context.repo.owner, From 606c2447c56b92157826018b27bde87a535e8eb7 Mon Sep 17 00:00:00 2001 From: "peco-engineer-bot[bot]" Date: Mon, 27 Jul 2026 03:45:55 +0000 Subject: [PATCH 05/10] ai: apply changes for #882 (1 review thread) Addresses: - #3654256027 at .github/workflows/skip-checks-reporter.yml:39 Signed-off-by: peco-engineer-bot[bot] --- .github/workflows/skip-checks-reporter.yml | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/.github/workflows/skip-checks-reporter.yml b/.github/workflows/skip-checks-reporter.yml index fcb8f7457..8f2831e11 100644 --- a/.github/workflows/skip-checks-reporter.yml +++ b/.github/workflows/skip-checks-reporter.yml @@ -42,9 +42,64 @@ jobs: labels: linux-ubuntu-latest permissions: checks: write + contents: read + pull-requests: read steps: + # The reporter triggers on EVERY pull_request run of "Trigger Integration + # Tests" (opened/synchronize/reopened/labeled/closed), but the + # workflow_run payload does NOT expose the originating sub-action, so we + # cannot filter on it directly. Recover the needed context from the PR + # and post the synthetic success only for the opened/synchronize/reopened + # scope the old inline job covered: + # - skip when the PR is closed (outside the old job's scope), and + # - skip when a real label preview is running for this head (an internal + # PR carrying the integration-test label): trigger-tests-pr dispatches + # the real suite and driver-test posts the real check for the same + # app + name + head_sha, so a premature success placeholder would race + # that check and could mask a real preview failure. + # Fork PRs KEEP the placeholder even when labeled: their label preview + # cannot dispatch (no secret access), so nothing else posts the required + # check for them. This is a read-only lookup (no checkout / no execution + # of PR-controlled content) and does not weaken the SECURITY note above. + - name: Decide whether to post the placeholder + id: gate + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + env: + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + with: + script: | + let prs = context.payload.workflow_run.pull_requests || []; + let number = prs.length ? prs[0].number : null; + if (number === null) { + // Fork PRs: workflow_run.pull_requests is empty. Resolve by SHA. + const { data } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: process.env.HEAD_SHA, + }); + number = data.length ? data[0].number : null; + } + if (number === null) { + // No PR resolvable (unexpected) — default to posting so the + // required check isn't left unfulfilled. + core.setOutput('post', 'true'); + return; + } + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: number, + }); + const isFork = pr.head.repo.full_name !== `${context.repo.owner}/${context.repo.repo}`; + const hasLabel = pr.labels.some(l => l.name === 'integration-test'); + const isClosed = pr.state === 'closed'; + const skip = isClosed || (hasLabel && !isFork); + console.log(`PR #${number} fork=${isFork} label=${hasLabel} closed=${isClosed} -> post=${!skip}`); + core.setOutput('post', (!skip).toString()); + - name: Generate GitHub App token (this repo) id: app-token + if: steps.gate.outputs.post == 'true' uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.INTEGRATION_TEST_APP_ID }} @@ -53,6 +108,7 @@ jobs: repositories: databricks-sql-python - name: Post skipped Python Integration Tests check + if: steps.gate.outputs.post == 'true' uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 env: HEAD_SHA: ${{ github.event.workflow_run.head_sha }} From f82205e905f978690c6c1d0f73916aac1c09bcb7 Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Sun, 26 Jul 2026 21:03:22 -0700 Subject: [PATCH 06/10] ci: post PR-open skip check inline for internal PRs (fix waiting-for-status hang) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the previous commit. Removing the inline PR-open poster in favour of the workflow_run reporter created a bootstrap gap: workflow_run workflows only run from the DEFAULT-branch copy, so on this PR (and any introducing PR) the reporter can't fire and NOTHING posts 'Python Integration Tests' on the PR head — the required check sits 'Expected — Waiting for status to be reported' forever. Fix: split the placeholder by PR origin, both posting as the driver-test app (the identity the ruleset pins the required check to): - Internal PRs: restore skip-integration-tests-pr in trigger-integration-tests.yml, now minting the INTEGRATION_TEST_APP token (not github.token) and self-guarded to head repo == base repo. Posts from the PR branch immediately — no label, no workflow_run dependency. This is what unblocks internal PRs like this one. - Fork PRs: skip-checks-reporter.yml (workflow_run) now self-guards to fork runs (head repo != base repo), since a fork's pull_request run can't mint secrets inline. The two guards are mutually exclusive — no double-post. Behaviour matches the intent: skipped placeholder on PR open, real run in the merge queue (and label preview). The merge-queue app-token posts from the prior commit are unchanged. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- .github/workflows/skip-checks-reporter.yml | 72 +++++++++---------- .../workflows/trigger-integration-tests.yml | 65 ++++++++++++++--- 2 files changed, 88 insertions(+), 49 deletions(-) diff --git a/.github/workflows/skip-checks-reporter.yml b/.github/workflows/skip-checks-reporter.yml index 8f2831e11..65f92de6d 100644 --- a/.github/workflows/skip-checks-reporter.yml +++ b/.github/workflows/skip-checks-reporter.yml @@ -1,21 +1,24 @@ name: Report Integration Test Skip # Posts the PR-open "skipped" placeholder for the required -# `Python Integration Tests` check, as the driver-test GitHub App. +# `Python Integration Tests` check on FORK PRs, as the driver-test GitHub App. # -# Why a separate workflow (and not an inline job in trigger-integration-tests.yml): -# - The branch ruleset pins the required `Python Integration Tests` check to the -# driver-test app's integration id (driver-integration-test). Only a check -# posted BY that app satisfies the gate — a `github.token` (github-actions) -# check of the same name does NOT (it is a different check context). -# - A PR from a fork runs its `pull_request` workflows with a READ-ONLY GITHUB_TOKEN -# and no access to secrets, so it cannot mint the app token and cannot post any -# check on its own head. That would leave fork PRs unable to enter the merge queue -# without a maintainer label. -# - `workflow_run` workflows always execute in THIS (base) repo's context using the -# workflow definition from the default branch, with full secret access — even when -# the run that triggered them came from a fork. That lets us post the app-attributed -# placeholder on any PR head, fork or not, so every PR can auto-enqueue. +# Division of labour: +# - INTERNAL PRs: the placeholder is posted inline by trigger-integration-tests.yml's +# `skip-integration-tests-pr` job (it can mint the app token directly). Posting it +# there means it works from the PR branch immediately, without waiting for this +# workflow_run file to reach the default branch. +# - FORK PRs: a fork's `pull_request` run has a READ-ONLY GITHUB_TOKEN and no secrets, +# so it cannot mint the app token or post any check on its own head. This +# workflow_run workflow instead runs in THIS (base) repo's context with full secret +# access even for fork-triggered runs, so it can post the app-attributed placeholder +# on a fork PR's head. This job self-guards to fork runs to avoid double-posting on +# internal PRs (which the inline job already covers). +# +# Why the app (not github.token): the ruleset pins the required +# `Python Integration Tests` check to the driver-test app's integration id. Only a +# check posted BY that app satisfies the gate — a github.token (github-actions) check +# of the same name is a different context and does NOT. # # SECURITY: this workflow runs with secrets in a privileged context. It MUST NOT check # out or execute any PR/fork-controlled content. It only calls checks.create with a @@ -25,8 +28,7 @@ name: Report Integration Test Skip # # The real integration suite is unaffected: it runs as the required gate on the # `merge_group` commit (and as a label preview on internal PRs), dispatched by -# trigger-integration-tests.yml. This workflow only posts the pre-merge placeholder. -# Mirrors databricks-sql-go / databricks-sql-nodejs. +# trigger-integration-tests.yml. Mirrors databricks-sql-go / databricks-sql-nodejs. on: workflow_run: @@ -35,8 +37,11 @@ on: jobs: report-skip: - # Only for PR-triggered runs; the merge_group run posts the real required check. - if: github.event.workflow_run.event == 'pull_request' + # Fork PR-triggered runs only. Internal PRs are posted inline by + # trigger-integration-tests.yml; the merge_group run posts the real required check. + if: >- + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name runs-on: group: databricks-protected-runner-group labels: linux-ubuntu-latest @@ -45,22 +50,14 @@ jobs: contents: read pull-requests: read steps: - # The reporter triggers on EVERY pull_request run of "Trigger Integration - # Tests" (opened/synchronize/reopened/labeled/closed), but the - # workflow_run payload does NOT expose the originating sub-action, so we - # cannot filter on it directly. Recover the needed context from the PR - # and post the synthetic success only for the opened/synchronize/reopened - # scope the old inline job covered: - # - skip when the PR is closed (outside the old job's scope), and - # - skip when a real label preview is running for this head (an internal - # PR carrying the integration-test label): trigger-tests-pr dispatches - # the real suite and driver-test posts the real check for the same - # app + name + head_sha, so a premature success placeholder would race - # that check and could mask a real preview failure. - # Fork PRs KEEP the placeholder even when labeled: their label preview - # cannot dispatch (no secret access), so nothing else posts the required - # check for them. This is a read-only lookup (no checkout / no execution - # of PR-controlled content) and does not weaken the SECURITY note above. + # This job is fork-only (see the job `if:`), so the placeholder is always + # appropriate EXCEPT when the fork PR is already closed. A fork PR's label + # preview cannot dispatch the real suite (no secret access), so nothing + # else posts the required check for it — keep the placeholder even when + # labeled. Skip only closed PRs. Resolve the PR by SHA because a fork's + # workflow_run payload has an empty pull_requests array. Read-only lookup + # (no checkout / no execution of PR content) — does not weaken the SECURITY + # note above. - name: Decide whether to post the placeholder id: gate uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 @@ -90,12 +87,9 @@ jobs: repo: context.repo.repo, pull_number: number, }); - const isFork = pr.head.repo.full_name !== `${context.repo.owner}/${context.repo.repo}`; - const hasLabel = pr.labels.some(l => l.name === 'integration-test'); const isClosed = pr.state === 'closed'; - const skip = isClosed || (hasLabel && !isFork); - console.log(`PR #${number} fork=${isFork} label=${hasLabel} closed=${isClosed} -> post=${!skip}`); - core.setOutput('post', (!skip).toString()); + console.log(`PR #${number} closed=${isClosed} -> post=${!isClosed}`); + core.setOutput('post', (!isClosed).toString()); - name: Generate GitHub App token (this repo) id: app-token diff --git a/.github/workflows/trigger-integration-tests.yml b/.github/workflows/trigger-integration-tests.yml index f717964b8..0c7275839 100644 --- a/.github/workflows/trigger-integration-tests.yml +++ b/.github/workflows/trigger-integration-tests.yml @@ -116,16 +116,61 @@ jobs: body: body }); - # NOTE: the PR-open "skipped" placeholder for the required - # `Python Integration Tests` check is NOT posted here. It is posted by the - # companion workflow `skip-checks-reporter.yml`, which runs on `workflow_run` - # in the base-repo context so it can post the check as the driver-test app — - # the app the branch ruleset PINS the required check to — on EVERY PR head, - # including fork PRs (whose own `pull_request` run gets a read-only token and - # cannot post checks at all). A `pull_request`-triggered `github.token` stub - # here posted the check as `github-actions`, which is a DIFFERENT check - # context and did NOT satisfy the app-pinned gate — so the required check sat - # unfulfilled and PRs stayed blocked. See skip-checks-reporter.yml. + # ============================================================================= + # For internal PRs: post the "skipped" placeholder for the required + # `Python Integration Tests` check on non-label events. The real run happens in + # the merge queue (or via explicit label preview). + # + # CRITICAL: post as the driver-test APP, not github.token. The ruleset pins the + # required check to that app's integration id; a github.token (github-actions) + # check of the same name is a DIFFERENT context and does NOT satisfy the gate, + # so the required check would sit "waiting for status" forever. + # + # Fork PRs are handled by the companion `skip-checks-reporter.yml` (workflow_run): + # a fork's `pull_request` run has a read-only token and no secrets, so it can + # neither mint the app token nor post any check here. This job self-guards to + # internal PRs (head repo == base repo); the reporter covers forks from the + # base-repo context. Internal PRs are posted HERE (not via the reporter) so the + # placeholder appears without depending on the workflow_run copy being on the + # default branch. + # ============================================================================= + skip-integration-tests-pr: + if: | + github.event_name == 'pull_request' && + github.event.action != 'labeled' && + github.event.action != 'closed' && + github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name + runs-on: + group: databricks-protected-runner-group + labels: linux-ubuntu-latest + steps: + - name: Generate GitHub App token (this repo) + id: app-token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + with: + app-id: ${{ secrets.INTEGRATION_TEST_APP_ID }} + private-key: ${{ secrets.INTEGRATION_TEST_PRIVATE_KEY }} + owner: databricks + repositories: databricks-sql-python + + - name: Skip Python Integration Tests + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'Python Integration Tests', + head_sha: context.payload.pull_request.head.sha, + status: 'completed', + conclusion: 'success', + completed_at: new Date().toISOString(), + output: { + title: 'Skipped on PR — runs in merge queue', + summary: 'Python Integration Tests are skipped on PRs and run as the required gate in the merge queue. Add the `integration-test` label to preview them on this PR.' + } + }); # ============================================================================= # For PRs: Dispatch real tests when integration-test label is added. From 3c50b751828a6346cda90cb2f85674a2adfdd25c Mon Sep 17 00:00:00 2001 From: "peco-engineer-bot[bot]" Date: Mon, 27 Jul 2026 04:13:09 +0000 Subject: [PATCH 07/10] ai: apply changes for #882 (1 review thread) Addresses: - #3654339217 at .github/workflows/engineer-bot-learning.yml:34 Signed-off-by: peco-engineer-bot[bot] --- .github/workflows/engineer-bot-learning.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/engineer-bot-learning.yml b/.github/workflows/engineer-bot-learning.yml index df9054b2b..620a2f8dc 100644 --- a/.github/workflows/engineer-bot-learning.yml +++ b/.github/workflows/engineer-bot-learning.yml @@ -24,7 +24,7 @@ on: workflow_dispatch: inputs: since: - description: 'ISO lower bound to bound (shorten) the window and recover a wedged flow. Empty = adaptive cursor.' + description: 'ISO lower bound to shorten the window and recover a wedged flow. Empty = adaptive cursor.' type: string default: '' window-hours: From 6f26bf4304d1e9f81943f4f4b3ebd14a7f3175e9 Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Sun, 26 Jul 2026 21:13:54 -0700 Subject: [PATCH 08/10] ci: clarify merge-queue mint-failure is fail-closed (review r3654295072) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Soften the overstated 'the mint always succeeds here' comment and document the mint-failure case explicitly: it is uncloseable in-workflow (only the pinned app can post to the pinned context; a github.token fallback lands on a different context and neither satisfies nor fails the gate), but it is fail-CLOSED — the merge queue's check_response_timeout evicts an entry whose required check never reports, and the mint failure shows as a red run. No code change; comment only. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- .github/workflows/trigger-integration-tests.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/trigger-integration-tests.yml b/.github/workflows/trigger-integration-tests.yml index 0c7275839..f7d09be2a 100644 --- a/.github/workflows/trigger-integration-tests.yml +++ b/.github/workflows/trigger-integration-tests.yml @@ -346,7 +346,18 @@ jobs: # dispatch-failure — must be attributed to the same app, or it lands on a # different check context and never satisfies the pinned gate (leaving the # merge queue stuck). merge_group runs on the base repo with full secret - # access, so the mint always succeeds here. + # access, so the mint normally succeeds here. + # + # If the mint ITSELF fails (secret rotation, app uninstall, transient + # create-github-app-token error) the job aborts before it can post any + # check. That case is uncloseable in-workflow — only this app can post to + # the app-pinned context, so no fallback identity (github.token included) + # can substitute; a same-named github.token check lands on a DIFFERENT + # context and neither satisfies nor fails the gate. It is fail-CLOSED, not a + # bypass: the merge queue's check_response_timeout evicts an entry whose + # required check never reports (it does not merge), and a mint failure also + # surfaces as a red workflow run. Mitigation is out-of-band (alerting on the + # token-mint step), not another in-workflow check. - name: Generate GitHub App Token (internal repo) id: app-token uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 From 518d4f309313f3f188b450f3750c0bbe09af24b9 Mon Sep 17 00:00:00 2001 From: "peco-engineer-bot[bot]" Date: Mon, 27 Jul 2026 04:35:26 +0000 Subject: [PATCH 09/10] ai: apply changes for #882 (1 review thread) Addresses: - #3654417922 at .github/workflows/trigger-integration-tests.yml:366 Signed-off-by: peco-engineer-bot[bot] --- .github/workflows/trigger-integration-tests.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/trigger-integration-tests.yml b/.github/workflows/trigger-integration-tests.yml index f7d09be2a..e4ab7a6bb 100644 --- a/.github/workflows/trigger-integration-tests.yml +++ b/.github/workflows/trigger-integration-tests.yml @@ -365,7 +365,14 @@ jobs: app-id: ${{ secrets.INTEGRATION_TEST_APP_ID }} private-key: ${{ secrets.INTEGRATION_TEST_PRIVATE_KEY }} owner: databricks - repositories: databricks-driver-test + # Two repos: dispatch targets databricks-driver-test, while the + # auto-pass / dispatch-failure steps post the pinned check-run onto + # THIS repo (context.repo == databricks-sql-python in a merge_group + # run). A token scoped to driver-test alone 403s on those + # checks.create calls, leaving the required gate unposted. + repositories: | + databricks-driver-test + databricks-sql-python - name: Check if driver files changed id: changed From 990e404919febb3f8e53ed005b5cde8ce95a521f Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Sun, 26 Jul 2026 22:16:36 -0700 Subject: [PATCH 10/10] ci: refresh check state (re-post required checks on a clean head) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No file changes. The prior head accumulated many check-suites across repeated force-push rebases, and GitHub's mergeability rollup stopped surfacing the required 'Kernel E2E' context even though a valid github-actions success check-run existed on the commit — leaving the PR unable to enter the merge queue ('Kernel E2E — Expected — Waiting for status to be reported'). Pushing a fresh non-force commit gives a clean check-suite set so skip-kernel-e2e-pr re-posts Kernel E2E into the current suite and the rollup picks it up. Co-authored-by: Isaac Signed-off-by: eric-wang-1990