From 1707687bc01e81253aed6cbde74146b15aabec57 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Mon, 31 Aug 2026 16:00:51 +0200 Subject: [PATCH 1/6] fix(ci): review PRs with grokBuildText instead of grokText --- .github/workflows/ai-review.yml | 6 +++++- agent-scripts/ai-review/README.md | 2 +- agent-scripts/ai-review/run.ts | 26 +++++++++++++++++++++----- package.json | 4 +++- pnpm-lock.yaml | 10 ++++++++-- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index dcf32685f..962ed6dd8 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -43,8 +43,12 @@ jobs: persist-credentials: false - name: Setup Tools uses: TanStack/config/.github/setup@190f659075ff0845850e330883eb26d7ffd0671f # main + - name: Install Grok CLI + run: | + (curl -fsSL https://x.ai/cli/install.sh || curl -fsSL https://storage.googleapis.com/grok-build-public-artifacts/cli/install.sh) | bash + echo "$HOME/.grok/bin" >> "$GITHUB_PATH" - name: Build packages - run: pnpm exec nx run @tanstack/ai-grok:build + run: pnpm exec nx run @tanstack/ai-grok-build:build - name: Add PR head worktree env: AI_REVIEW_TOKEN: ${{ secrets.AI_REVIEW_TOKEN }} diff --git a/agent-scripts/ai-review/README.md b/agent-scripts/ai-review/README.md index 8269bf8f6..8ae6e6000 100644 --- a/agent-scripts/ai-review/README.md +++ b/agent-scripts/ai-review/README.md @@ -1,6 +1,6 @@ # Grok PR review bot -A GitHub Action that reviews open pull requests with TanStack AI (`chat()` + `grokText('grok-4.6')` + high reasoning). It comments, sets one `ai-*` label, and can push listed polish commits. +A GitHub Action that reviews open pull requests with TanStack AI (`chat()` + `grokBuildText('composer-2.5')`). It comments, sets one `ai-*` label, and can push listed polish commits. The first lines of every bot comment say the comment is automated. It is not a maintainer review. The bot never GitHub-approves and never merges. diff --git a/agent-scripts/ai-review/run.ts b/agent-scripts/ai-review/run.ts index 606432b33..4d94202d7 100644 --- a/agent-scripts/ai-review/run.ts +++ b/agent-scripts/ai-review/run.ts @@ -8,7 +8,9 @@ import { resolve } from 'node:path' import process from 'node:process' import { fileURLToPath } from 'node:url' import { chat } from '@tanstack/ai' -import { grokText } from '@tanstack/ai-grok' +import { grokBuildText } from '@tanstack/ai-grok-build' +import { defineSandbox, withSandbox } from '@tanstack/ai-sandbox' +import { localProcessSandbox } from '@tanstack/ai-sandbox-local-process' import { loadConfig, isRosterMaintainer, @@ -119,21 +121,35 @@ type ReviewInput = { /** * Production Grok review step for `runReviewJob`. * - * Tools run first (read/edit files). Then `outputSchema` returns the verdict. - * Do not call this from unit tests. + * `grokBuildText` streams tools first, then a `structured-output.complete` + * event. Do not call this from unit tests. */ export function createGrokReview() { return async (input: ReviewInput) => { + const sandbox = defineSandbox({ + id: 'ai-review', + provider: localProcessSandbox({ + dir: input.worktreeRoot, + removeOnDestroy: false, + }), + lifecycle: { reuse: 'none', destroyOnComplete: false }, + }) const result = await chat({ - adapter: grokText('grok-4.6'), - modelOptions: { reasoning: { effort: 'high' } }, + adapter: grokBuildText('composer-2.5', { + authMode: 'api-key', + protocol: 'streaming-json', + cwd: input.worktreeRoot, + }), tools: createReviewTools({ worktreeRoot: input.worktreeRoot }), outputSchema: reviewVerdictSchema, + middleware: [withSandbox(sandbox)], + threadId: `ai-review-${input.pr.number}`, messages: [ { role: 'user', content: [ 'Review this pull request.', + 'Read the changed source files before you choose a verdict.', 'If it is a bug fix and does not fix the claimed root cause, verdict is reject.', 'If it is useful and needs listed bug or suggestion edits, apply those with edit_file, then verdict polish.', 'If it is useful and clean, verdict is ready.', diff --git a/package.json b/package.json index b5087bf7e..77d9e58bd 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,9 @@ "@faker-js/faker": "^10.1.0", "@stylistic/eslint-plugin": "^5.10.0", "@tanstack/ai": "workspace:*", - "@tanstack/ai-grok": "workspace:*", + "@tanstack/ai-grok-build": "workspace:*", + "@tanstack/ai-sandbox": "workspace:*", + "@tanstack/ai-sandbox-local-process": "workspace:*", "@tanstack/typedoc-config": "0.3.4", "@tanstack/vite-config": "0.6.0", "@types/node": "^24.10.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76f8cf462..a05386a96 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,9 +34,15 @@ importers: '@tanstack/ai': specifier: workspace:* version: link:packages/ai - '@tanstack/ai-grok': + '@tanstack/ai-grok-build': + specifier: workspace:* + version: link:packages/ai-grok-build + '@tanstack/ai-sandbox': + specifier: workspace:* + version: link:packages/ai-sandbox + '@tanstack/ai-sandbox-local-process': specifier: workspace:* - version: link:packages/ai-grok + version: link:packages/ai-sandbox-local-process '@tanstack/typedoc-config': specifier: 0.3.4 version: 0.3.4 From beb06c2084bceed6acc5b486b99c5a62a13a52dc Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Mon, 31 Aug 2026 16:03:14 +0200 Subject: [PATCH 2/6] fix(ci): use grok-4.6 with grokBuildText for PR review --- agent-scripts/ai-review/README.md | 2 +- agent-scripts/ai-review/run.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/agent-scripts/ai-review/README.md b/agent-scripts/ai-review/README.md index 8ae6e6000..bbf0ddd64 100644 --- a/agent-scripts/ai-review/README.md +++ b/agent-scripts/ai-review/README.md @@ -1,6 +1,6 @@ # Grok PR review bot -A GitHub Action that reviews open pull requests with TanStack AI (`chat()` + `grokBuildText('composer-2.5')`). It comments, sets one `ai-*` label, and can push listed polish commits. +A GitHub Action that reviews open pull requests with TanStack AI (`chat()` + `grokBuildText('grok-4.6')`). It comments, sets one `ai-*` label, and can push listed polish commits. The first lines of every bot comment say the comment is automated. It is not a maintainer review. The bot never GitHub-approves and never merges. diff --git a/agent-scripts/ai-review/run.ts b/agent-scripts/ai-review/run.ts index 4d94202d7..f0415b4cc 100644 --- a/agent-scripts/ai-review/run.ts +++ b/agent-scripts/ai-review/run.ts @@ -135,7 +135,7 @@ export function createGrokReview() { lifecycle: { reuse: 'none', destroyOnComplete: false }, }) const result = await chat({ - adapter: grokBuildText('composer-2.5', { + adapter: grokBuildText('grok-4.6', { authMode: 'api-key', protocol: 'streaming-json', cwd: input.worktreeRoot, From 9aac1452cf7445fe2134353d7d398452effad918 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Mon, 31 Aug 2026 16:07:25 +0200 Subject: [PATCH 3/6] fix(ci): install Grok CLI in the review sandbox setup --- .github/workflows/ai-review.yml | 4 ---- agent-scripts/ai-review/README.md | 1 + agent-scripts/ai-review/run.ts | 25 ++++++++++++++++++++++--- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 962ed6dd8..b77109fc1 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -43,10 +43,6 @@ jobs: persist-credentials: false - name: Setup Tools uses: TanStack/config/.github/setup@190f659075ff0845850e330883eb26d7ffd0671f # main - - name: Install Grok CLI - run: | - (curl -fsSL https://x.ai/cli/install.sh || curl -fsSL https://storage.googleapis.com/grok-build-public-artifacts/cli/install.sh) | bash - echo "$HOME/.grok/bin" >> "$GITHUB_PATH" - name: Build packages run: pnpm exec nx run @tanstack/ai-grok-build:build - name: Add PR head worktree diff --git a/agent-scripts/ai-review/README.md b/agent-scripts/ai-review/README.md index bbf0ddd64..952600f16 100644 --- a/agent-scripts/ai-review/README.md +++ b/agent-scripts/ai-review/README.md @@ -46,6 +46,7 @@ Open the **AI review** workflow log. Common causes: - Missing `AI_REVIEW_TOKEN` or `XAI_API_KEY` - Fork with maintainer edits off (comment is posted, label is `ai-needs-work`, no push) - `chat()` did not return a valid verdict object +- Workspace setup failed to install the Grok CLI ## Layout diff --git a/agent-scripts/ai-review/run.ts b/agent-scripts/ai-review/run.ts index f0415b4cc..031060772 100644 --- a/agent-scripts/ai-review/run.ts +++ b/agent-scripts/ai-review/run.ts @@ -3,13 +3,23 @@ */ import { spawn } from 'node:child_process' +import { homedir } from 'node:os' import { readFile } from 'node:fs/promises' -import { resolve } from 'node:path' +import { join, resolve } from 'node:path' import process from 'node:process' import { fileURLToPath } from 'node:url' import { chat } from '@tanstack/ai' -import { grokBuildText } from '@tanstack/ai-grok-build' -import { defineSandbox, withSandbox } from '@tanstack/ai-sandbox' +import { + GROK_CLI_INSTALL_COMMAND, + grokBuildText, +} from '@tanstack/ai-grok-build' +import { + createSecrets, + defineSandbox, + defineWorkspace, + localSource, + withSandbox, +} from '@tanstack/ai-sandbox' import { localProcessSandbox } from '@tanstack/ai-sandbox-local-process' import { loadConfig, @@ -126,12 +136,20 @@ type ReviewInput = { */ export function createGrokReview() { return async (input: ReviewInput) => { + const xaiKey = process.env.XAI_API_KEY const sandbox = defineSandbox({ id: 'ai-review', provider: localProcessSandbox({ dir: input.worktreeRoot, removeOnDestroy: false, }), + workspace: defineWorkspace({ + source: localSource(input.worktreeRoot), + setup: ({ serial }) => serial(GROK_CLI_INSTALL_COMMAND), + ...(xaiKey !== undefined && xaiKey.length > 0 + ? { secrets: createSecrets({ XAI_API_KEY: xaiKey }) } + : {}), + }), lifecycle: { reuse: 'none', destroyOnComplete: false }, }) const result = await chat({ @@ -139,6 +157,7 @@ export function createGrokReview() { authMode: 'api-key', protocol: 'streaming-json', cwd: input.worktreeRoot, + grokExecutable: join(homedir(), '.grok', 'bin', 'grok'), }), tools: createReviewTools({ worktreeRoot: input.worktreeRoot }), outputSchema: reviewVerdictSchema, From 4eca0d495025f8c70e063eb8c61630513517a31c Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Mon, 31 Aug 2026 16:21:16 +0200 Subject: [PATCH 4/6] fix(ci): skip auto AI review on maintainer PRs --- .github/workflows/ai-review.yml | 4 +++- agent-scripts/ai-review/README.md | 5 +++-- agent-scripts/ai-review/run.test.ts | 13 ++++++++++++- agent-scripts/ai-review/skip.test.ts | 20 ++++++++++++++++++-- agent-scripts/ai-review/skip.ts | 14 +++++++++++--- 5 files changed, 47 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index b77109fc1..a5ca20c1d 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -26,7 +26,9 @@ jobs: review: name: Review if: > - (github.event_name == 'pull_request' && github.event.pull_request.draft == false) + (github.event_name == 'pull_request' + && github.event.pull_request.draft == false + && !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)) || github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && github.event.issue.pull_request diff --git a/agent-scripts/ai-review/README.md b/agent-scripts/ai-review/README.md index 952600f16..ae39df02f 100644 --- a/agent-scripts/ai-review/README.md +++ b/agent-scripts/ai-review/README.md @@ -15,11 +15,12 @@ Until both secrets exist, the job fails with `missing AI_REVIEW_TOKEN or XAI_API ## How a run starts -- Auto: `pull_request` opened, synchronize, or ready_for_review (non-draft). +- Auto: `pull_request` opened, synchronize, or ready_for_review, when the PR is not a draft. +- Auto does not start when the PR author is OWNER, MEMBER, or COLLABORATOR. GitHub then shows a skipped check, not a cancelled check. - Manual: Actions `workflow_dispatch` with a PR number. - Manual: a login in `.github/maintainers.json` comments `/ai-review` on the PR. -Auto skips drafts, bot PRs, the machine user's own head commit, and a head SHA this bot already reviewed. Manual still runs on those, except it never executes PR code. +Auto also skips drafts, bot PRs, roster-maintainer PRs, the machine user's own head commit, and a head SHA this bot already reviewed. Manual still runs on those. The bot never executes PR code. ## Labels diff --git a/agent-scripts/ai-review/run.test.ts b/agent-scripts/ai-review/run.test.ts index 126ade4b1..806efb98a 100644 --- a/agent-scripts/ai-review/run.test.ts +++ b/agent-scripts/ai-review/run.test.ts @@ -23,6 +23,7 @@ function samplePull( overrides: { draft?: boolean maintainer_can_modify?: boolean + login?: string } = {}, ) { return { @@ -31,7 +32,7 @@ function samplePull( body: 'Handle empty messages.', html_url: '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/TanStack/ai/pull/42', draft: overrides.draft ?? false, - user: { login: 'alice' }, + user: { login: overrides.login ?? 'alice' }, head: { sha: SHA, ref: HEAD_REF, @@ -293,6 +294,16 @@ describe('runReviewJob', () => { expect(gitCalls).toEqual([]) }) + it('skips an auto run from a roster maintainer and does not post a comment', async () => { + const { result, comments, gitCalls } = await runJob({ + pull: samplePull({ login: 'alem' }), + }) + + expect(result).toEqual({ skipped: true, reason: 'maintainer-author' }) + expect(comments).toEqual([]) + expect(gitCalls).toEqual([]) + }) + it('skips an issue_comment that is not the /ai-review command', async () => { const { result, comments, gitCalls } = await runJob({ eventName: 'issue_comment', diff --git a/agent-scripts/ai-review/skip.test.ts b/agent-scripts/ai-review/skip.test.ts index 41226755f..5c027f054 100644 --- a/agent-scripts/ai-review/skip.test.ts +++ b/agent-scripts/ai-review/skip.test.ts @@ -49,6 +49,13 @@ describe('shouldSkip', () => { }) }) + it('skips a roster maintainer PR author', () => { + expect(reviewSkip({ authorLogin: 'alem' })).toEqual({ + skip: true, + reason: 'maintainer-author', + }) + }) + it('skips when the head commit is from the machine user', () => { expect(reviewSkip({ headCommitAuthorLogin: 'TanStack-AI-Bot' })).toEqual({ skip: true, @@ -100,6 +107,15 @@ describe('shouldSkip', () => { ).toEqual({ skip: true, reason: 'bot-author' }) }) + it('prefers maintainer-author over bot-head-commit', () => { + expect( + reviewSkip({ + authorLogin: 'alem', + headCommitAuthorLogin: MACHINE, + }), + ).toEqual({ skip: true, reason: 'maintainer-author' }) + }) + it('prefers bot-head-commit over same-sha', () => { expect( reviewSkip({ @@ -111,12 +127,12 @@ describe('shouldSkip', () => { }) describe('manual', () => { - it('never skips for draft, bot author, bot head commit, or same SHA', () => { + it('never skips for draft, bot author, maintainer author, bot head commit, or same SHA', () => { expect( reviewSkip({ mode: 'manual', isDraft: true, - authorLogin: 'renovate', + authorLogin: 'alem', headCommitAuthorLogin: MACHINE, alreadyReviewedSha: SHA, }), diff --git a/agent-scripts/ai-review/skip.ts b/agent-scripts/ai-review/skip.ts index 1362a5445..d016449f8 100644 --- a/agent-scripts/ai-review/skip.ts +++ b/agent-scripts/ai-review/skip.ts @@ -1,4 +1,7 @@ -import { isBotLogin } from '../../scripts/maintainer/config.ts' +import { + isBotLogin, + isRosterMaintainer, +} from '../../scripts/maintainer/config.ts' import type { ToolsetConfig } from '../../scripts/maintainer/types.ts' type SkipInput = { @@ -15,8 +18,9 @@ type SkipInput = { /** * Decide whether this AI review run should skip, and why. * - * Auto mode skips drafts, bot PR authors, machine-user head commits, and a - * head SHA that was already reviewed, in that order. Manual mode never skips. + * Auto mode skips drafts, bot PR authors, roster maintainers, machine-user + * head commits, and a head SHA that was already reviewed, in that order. + * Manual mode never skips. */ export function shouldSkip(input: SkipInput) { if (input.mode === 'manual') { @@ -31,6 +35,10 @@ export function shouldSkip(input: SkipInput) { return { skip: true, reason: 'bot-author' } } + if (isRosterMaintainer(input.authorLogin, input.config)) { + return { skip: true, reason: 'maintainer-author' } + } + const isBotHeadCommit = input.headCommitAuthorLogin !== null && input.headCommitAuthorLogin.toLowerCase() === From 8734c6fb7939d4b05e8708d5514cc1e131a60c6b Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Mon, 31 Aug 2026 16:25:38 +0200 Subject: [PATCH 5/6] fix(ci): skip maintainer auto review in the job if --- .github/workflows/ai-review.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index a5ca20c1d..218833071 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -25,14 +25,7 @@ permissions: {} jobs: review: name: Review - if: > - (github.event_name == 'pull_request' - && github.event.pull_request.draft == false - && !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)) - || github.event_name == 'workflow_dispatch' - || (github.event_name == 'issue_comment' - && github.event.issue.pull_request - && startsWith(github.event.comment.body, '/ai-review')) + if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ai-review')) || (github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.event.pull_request.author_association != 'OWNER' && github.event.pull_request.author_association != 'MEMBER' && github.event.pull_request.author_association != 'COLLABORATOR') }} runs-on: ubuntu-latest timeout-minutes: 30 permissions: From a20c6af48f5fc8c6736005d4d7a6829c229b23df Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Mon, 31 Aug 2026 16:27:18 +0200 Subject: [PATCH 6/6] fix(ci): skip auto AI review by maintainer login --- .github/workflows/ai-review.yml | 11 ++++++++++- agent-scripts/ai-review/README.md | 3 ++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 218833071..65791ee40 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -25,7 +25,16 @@ permissions: {} jobs: review: name: Review - if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ai-review')) || (github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.event.pull_request.author_association != 'OWNER' && github.event.pull_request.author_association != 'MEMBER' && github.event.pull_request.author_association != 'COLLABORATOR') }} + if: > + (github.event_name == 'pull_request' + && github.event.pull_request.draft == false + && github.event.pull_request.user.login != 'AlemTuzlak' + && github.event.pull_request.user.login != 'tombeckenham' + && github.event.pull_request.user.login != 'jherr') + || github.event_name == 'workflow_dispatch' + || (github.event_name == 'issue_comment' + && github.event.issue.pull_request + && startsWith(github.event.comment.body, '/ai-review')) runs-on: ubuntu-latest timeout-minutes: 30 permissions: diff --git a/agent-scripts/ai-review/README.md b/agent-scripts/ai-review/README.md index ae39df02f..2d83dd41e 100644 --- a/agent-scripts/ai-review/README.md +++ b/agent-scripts/ai-review/README.md @@ -16,7 +16,8 @@ Until both secrets exist, the job fails with `missing AI_REVIEW_TOKEN or XAI_API ## How a run starts - Auto: `pull_request` opened, synchronize, or ready_for_review, when the PR is not a draft. -- Auto does not start when the PR author is OWNER, MEMBER, or COLLABORATOR. GitHub then shows a skipped check, not a cancelled check. +- Auto does not start when the PR author is AlemTuzlak, tombeckenham, or jherr. +- Keep those logins in sync with `.github/maintainers.json`. GitHub then shows a skipped check, not a cancelled check. - Manual: Actions `workflow_dispatch` with a PR number. - Manual: a login in `.github/maintainers.json` comments `/ai-review` on the PR.