Compare commits

..

27 Commits

Author SHA1 Message Date
gemini-cli-robot 55086b9790 chore(release): v0.22.2 2025-12-22 21:21:50 +00:00
N. Taylor Mullen 9bc9a31923 fix(core): use debugLogger.debug for startup profiler logs (#15443) 2025-12-22 16:05:16 -05:00
Abhi 999f9b4bf1 feat(policy): implement dynamic mode-aware policy evaluation (#15307) 2025-12-22 15:47:34 -05:00
Jacob Richman 81b171c1b4 fix(policy): fix bug where accepting-edits continued after it was turned off (#15351) 2025-12-22 13:40:04 -05:00
Jacob Richman f883f74ad5 feat(ui): Put "Allow for all future sessions" behind a setting off by default. (#15322) 2025-12-22 13:40:04 -05:00
N. Taylor Mullen 999516ddf7 feat: simplify tool confirmation labels for better UX (#15296) 2025-12-22 13:40:04 -05:00
gemini-cli-robot f9331b1602 chore(release): v0.22.0-preview.3 2025-12-19 01:45:33 +00:00
gemini-cli-robot ed4b440ba0 fix(patch): cherry-pick 9e6914d to release/v0.22.0-preview.2-pr-15288 to patch version v0.22.0-preview.2 and create version 0.22.0-preview.3 (#15294)
Co-authored-by: Sehoon Shon <sshon@google.com>
2025-12-19 00:07:18 +00:00
gemini-cli-robot a6841f41d2 chore(release): v0.22.0-preview.2 2025-12-17 23:52:10 +00:00
gemini-cli-robot a585bfa99e fix(patch): cherry-pick a6d1245 to release/v0.22.0-preview.1-pr-15214 to patch version v0.22.0-preview.1 and create version 0.22.0-preview.2 (#15226)
Co-authored-by: Sehoon Shon <sshon@google.com>
2025-12-17 23:24:56 +00:00
gemini-cli-robot c15547156d chore(release): v0.22.0-preview.1 2025-12-17 16:37:55 +00:00
Tommaso Sciortino 76414c1cc0 fix goldens 2025-12-17 08:19:36 -08:00
Tommaso Sciortino d1e040aa91 Fix settings.schema 2025-12-17 08:01:49 -08:00
Jenna Inouye ce2eba283b Docs: Update Gemini 3 Documentation (#91)
* Update docs.

* Update model docs.
2025-12-16 14:51:40 -08:00
Adib234 c5109d7531 change banner text for vertex and api key users (#90) 2025-12-16 14:51:07 -08:00
Adam Weidman dbeda91ec4 feat: update to match mocks (#89) 2025-12-16 14:50:26 -08:00
Sehoon Shon 4c0a24119b Check if a user has access to preview model (#87) 2025-12-16 14:49:42 -08:00
Tommaso Sciortino f25944bdc6 Fix prompt and chat code (#88) 2025-12-16 14:49:10 -08:00
joshualitt 4a83eb2402 feat(core): Add real model string (#86) 2025-12-16 14:48:32 -08:00
joshualitt 20e67c7bda feat(core): Disable todos for 3 family. (#85)
* feat(core): Disable todos for 3 family.

* tidy
2025-12-16 14:45:15 -08:00
Sehoon Shon 9ab79b712c Do not fallback for manual models (#84)
* Update display name for alias model

* fix tests
2025-12-16 14:44:42 -08:00
Sehoon Shon 16e06adb46 Use Preview Flash model if the main model is a preview model for code… (#83)
* Use Preview Flash model if the main model is a preview model for codebase investigator

* fix test

* Generate settings doc
2025-12-16 14:43:50 -08:00
Adam Weidman 48ad6983a3 FEAT: Add availabilityService (#81)
* auto and fallback work

* test fixes

* fixes

* Show model dialog even if there's no more fallback option

* fix tests

* fix failing test

* disable showInDialog for model in settings

* revert package-lock.json

* remove dup line

---------

Co-authored-by: Sehoon Shon <sshon@google.com>
2025-12-16 14:38:32 -08:00
Sehoon Shon ad994cfe8b Update models menu dialog to support manual model selection and group… (#80)
* Update models menu dialog to support manual model selection and group by model family

* fix tests

* update codebase investigator model setting

* fix test

* fix test

* update generated golden file

* regenerate scheme and doc for settings

* use Preview Auto if previewFeatures is set to true
2025-12-16 14:36:49 -08:00
joshualitt 5eb817c426 feat(core): Update checkpoint. (#79) 2025-12-16 14:35:32 -08:00
joshualitt 562d84545a feat(core): Add model config for flash 3 (#78) 2025-12-16 14:33:42 -08:00
gemini-cli-robot 06dcf216e1 chore(release): v0.22.0-preview.0 2025-12-16 18:17:25 +00:00
831 changed files with 21486 additions and 66443 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

-29
View File
@@ -1,29 +0,0 @@
description="Injects context of all relevant cli files"
prompt = """
The following output contains the complete
source code of packages/core.
**Pay extremely close attention to these files.** They define the project's
core architecture, component patterns, and testing standards.
The source code contains the content of absolutely every source code file in
packages/core.
You should very rarely need to read any other files from packages/core to resolve
prompts.
!{find packages/core \\( -path packages/cli/dist -o -path packages/core/dist -o -name node_modules \\) -prune -o -type f \\( -name "*.ts" -o -name "*.tsx" \\) ! -name "*.test.ts" ! -name "*.test.tsx" ! -name "*.d.ts" -exec echo "--- {} ---" \\; -exec cat {} \\;}
In addition to the code context, you MUST strictly adhere to the following frontend-specific development guidelines when writing code in packages/core.
## Configuration & Settings
* **Settings vs Args**: Use settings for user-configurable options; do not add new CLI arguments.
* **Schema**: Add new settings to `packages/cli/src/config/settingsSchema.ts`.
* **Documentation**:
* If `showInDialog: true`, document in `docs/get-started/configuration.md`.
* Ensure `requiresRestart` is correctly set.
## General
* **Logging**: Use `debugLogger` for errors. NEVER leave `console.log/warn/error` in the code.
* **TypeScript**: Avoid the non-null assertion operator (`!`).
{{args}}.
"""
-16
View File
@@ -1,16 +0,0 @@
description = "Analyze the influence of system instructions on a specific action."
prompt = """
# Introspection Task
Take a step back and analyze your own system instructions and internal logic.
The user is curious about the reasoning behind a specific action or decision you've made.
**Specific point of interest:** {{args}}
Please provide a detailed breakdown of:
1. Which parts of your system instructions (global, workspace-specific, or provided via GEMINI.md) influenced this behavior?
2. What was your internal thought process leading up to this action?
3. Are there any ambiguities or conflicting instructions that played a role?
Your goal is to provide transparency into your underlying logic so the user can potentially improve the instructions in the future.
"""
-5
View File
@@ -1,5 +0,0 @@
{
"experimental": {
"skills": true
}
}
-57
View File
@@ -1,57 +0,0 @@
---
name: pr-creator
description:
Use this skill when asked to create a pull request (PR). It ensures all PRs
follow the repository's established templates and standards.
---
# Pull Request Creator
This skill guides the creation of high-quality Pull Requests that adhere to the
repository's standards.
## Workflow
Follow these steps to create a Pull Request:
1. **Locate Template**: Search for a pull request template in the repository.
- Check `.github/pull_request_template.md`
- Check `.github/PULL_REQUEST_TEMPLATE.md`
- If multiple templates exist (e.g., in `.github/PULL_REQUEST_TEMPLATE/`),
ask the user which one to use or select the most appropriate one based on
the context (e.g., `bug_fix.md` vs `feature.md`).
2. **Read Template**: Read the content of the identified template file.
3. **Draft Description**: Create a PR description that strictly follows the
template's structure.
- **Headings**: Keep all headings from the template.
- **Checklists**: Review each item. Mark with `[x]` if completed. If an item
is not applicable, leave it unchecked or mark as `[ ]` (depending on the
template's instructions) or remove it if the template allows flexibility
(but prefer keeping it unchecked for transparency).
- **Content**: Fill in the sections with clear, concise summaries of your
changes.
- **Related Issues**: Link any issues fixed or related to this PR (e.g.,
"Fixes #123").
4. **Create PR**: Use the `gh` CLI to create the PR. To avoid shell escaping
issues with multi-line Markdown, write the description to a temporary file
first.
```bash
# 1. Write the drafted description to a temporary file
# 2. Create the PR using the --body-file flag
gh pr create --title "type(scope): succinct description" --body-file <temp_file_path>
# 3. Remove the temporary file
rm <temp_file_path>
```
- **Title**: Ensure the title follows the
[Conventional Commits](https://www.conventionalcommits.org/) format if the
repository uses it (e.g., `feat(ui): add new button`,
`fix(core): resolve crash`).
## Principles
- **Compliance**: Never ignore the PR template. It exists for a reason.
- **Completeness**: Fill out all relevant sections.
- **Accuracy**: Don't check boxes for tasks you haven't done.
+2
View File
@@ -1,5 +1,7 @@
name: 'Bug Report'
description: 'Report a bug to help us improve Gemini CLI'
labels:
- 'status/need-triage'
body:
- type: 'markdown'
attributes:
+20 -18
View File
@@ -1,33 +1,35 @@
# See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'weekly'
day: 'monday'
open-pull-requests-limit: 10
interval: 'daily'
target-branch: 'main'
commit-message:
prefix: 'chore(deps)'
include: 'scope'
reviewers:
- 'joshualitt'
- 'google-gemini/gemini-cli-askmode-approvers'
groups:
npm-dependencies:
patterns:
- '*'
# Group all non-major updates together.
# This is to reduce the number of PRs that need to be reviewed.
# Major updates will still be created as separate PRs.
npm-minor-patch:
applies-to: 'version-updates'
update-types:
- 'minor'
- 'patch'
open-pull-requests-limit: 0
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
day: 'monday'
open-pull-requests-limit: 10
interval: 'daily'
target-branch: 'main'
commit-message:
prefix: 'chore(deps)'
include: 'scope'
reviewers:
- 'joshualitt'
groups:
actions-dependencies:
patterns:
- '*'
update-types:
- 'minor'
- 'patch'
- 'google-gemini/gemini-cli-askmode-approvers'
open-pull-requests-limit: 0
-138
View File
@@ -1,138 +0,0 @@
/* eslint-disable */
/* global require, console, process */
/**
* Script to backfill the 'status/need-triage' label to all open issues
* that are NOT currently labeled with '🔒 maintainer only' or 'help wanted'.
*/
const { execFileSync } = require('child_process');
const isDryRun = process.argv.includes('--dry-run');
const REPO = 'google-gemini/gemini-cli';
/**
* Executes a GitHub CLI command safely using an argument array to prevent command injection.
* @param {string[]} args
* @returns {string|null}
*/
function runGh(args) {
try {
// Using execFileSync with an array of arguments is safe as it doesn't use a shell.
// We set a large maxBuffer (10MB) to handle repositories with many issues.
return execFileSync('gh', args, {
encoding: 'utf8',
maxBuffer: 10 * 1024 * 1024,
stdio: ['ignore', 'pipe', 'pipe'],
}).trim();
} catch (error) {
const stderr = error.stderr ? ` Stderr: ${error.stderr.trim()}` : '';
console.error(
`❌ Error running gh ${args.join(' ')}: ${error.message}${stderr}`,
);
return null;
}
}
async function main() {
console.log('🔐 GitHub CLI security check...');
const authStatus = runGh(['auth', 'status']);
if (authStatus === null) {
console.error('❌ GitHub CLI (gh) is not installed or not authenticated.');
process.exit(1);
}
if (isDryRun) {
console.log('🧪 DRY RUN MODE ENABLED - No changes will be made.\n');
}
console.log(`🔍 Fetching and filtering open issues from ${REPO}...`);
// We use the /issues endpoint with pagination to bypass the 1000-result limit.
// The jq filter ensures we exclude PRs, maintainer-only, help-wanted, and existing status/need-triage.
const jqFilter =
'.[] | select(.pull_request == null) | select([.labels[].name] as $l | (any($l[]; . == "🔒 maintainer only") | not) and (any($l[]; . == "help wanted") | not) and (any($l[]; . == "status/need-triage") | not)) | {number: .number, title: .title}';
const output = runGh([
'api',
`repos/${REPO}/issues?state=open&per_page=100`,
'--paginate',
'--jq',
jqFilter,
]);
if (output === null) {
process.exit(1);
}
const issues = output
.split('\n')
.filter((line) => line.trim())
.map((line) => {
try {
return JSON.parse(line);
} catch (_e) {
console.error(`⚠️ Failed to parse line: ${line}`);
return null;
}
})
.filter(Boolean);
console.log(`✅ Found ${issues.length} issues matching criteria.`);
if (issues.length === 0) {
console.log('✨ No issues need backfilling.');
return;
}
let successCount = 0;
let failCount = 0;
if (isDryRun) {
for (const issue of issues) {
console.log(
`[DRY RUN] Would label issue #${issue.number}: ${issue.title}`,
);
}
successCount = issues.length;
} else {
console.log(`🏷️ Applying labels to ${issues.length} issues...`);
for (const issue of issues) {
const issueNumber = String(issue.number);
console.log(`🏷️ Labeling issue #${issueNumber}: ${issue.title}`);
const result = runGh([
'issue',
'edit',
issueNumber,
'--add-label',
'status/need-triage',
'--repo',
REPO,
]);
if (result !== null) {
successCount++;
} else {
failCount++;
}
}
}
console.log(`\n📊 Summary:`);
console.log(` - Success: ${successCount}`);
console.log(` - Failed: ${failCount}`);
if (failCount > 0) {
console.error(`\n❌ Backfill completed with ${failCount} errors.`);
process.exit(1);
} else {
console.log(`\n🎉 ${isDryRun ? 'Dry run' : 'Backfill'} complete!`);
}
}
main().catch((error) => {
console.error('❌ Unexpected error:', error);
process.exit(1);
});
@@ -1,190 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/* eslint-disable */
/* global require, console, process */
/**
* Script to backfill a process change notification comment to all open PRs
* not created by members of the 'gemini-cli-maintainers' team.
*
* Skip PRs that are already associated with an issue.
*/
const { execFileSync } = require('child_process');
const isDryRun = process.argv.includes('--dry-run');
const REPO = 'google-gemini/gemini-cli';
const ORG = 'google-gemini';
const TEAM_SLUG = 'gemini-cli-maintainers';
const DISCUSSION_URL =
'https://github.com/google-gemini/gemini-cli/discussions/16706';
/**
* Executes a GitHub CLI command safely using an argument array.
*/
function runGh(args, options = {}) {
const { silent = false } = options;
try {
return execFileSync('gh', args, {
encoding: 'utf8',
maxBuffer: 10 * 1024 * 1024,
stdio: ['ignore', 'pipe', 'pipe'],
}).trim();
} catch (error) {
if (!silent) {
const stderr = error.stderr ? ` Stderr: ${error.stderr.trim()}` : '';
console.error(
`❌ Error running gh ${args.join(' ')}: ${error.message}${stderr}`,
);
}
return null;
}
}
/**
* Checks if a user is a member of the maintainers team.
*/
const membershipCache = new Map();
function isMaintainer(username) {
if (membershipCache.has(username)) return membershipCache.get(username);
// GitHub returns 404 if user is not a member.
// We use silent: true to avoid logging 404s as errors.
const result = runGh(
['api', `orgs/${ORG}/teams/${TEAM_SLUG}/memberships/${username}`],
{ silent: true },
);
const isMember = result !== null;
membershipCache.set(username, isMember);
return isMember;
}
async function main() {
console.log('🔐 GitHub CLI security check...');
if (runGh(['auth', 'status']) === null) {
console.error('❌ GitHub CLI (gh) is not authenticated.');
process.exit(1);
}
if (isDryRun) {
console.log('🧪 DRY RUN MODE ENABLED\n');
}
console.log(`📥 Fetching open PRs from ${REPO}...`);
// Fetch number, author, and closingIssuesReferences to check if linked to an issue
const prsJson = runGh([
'pr',
'list',
'--repo',
REPO,
'--state',
'open',
'--limit',
'1000',
'--json',
'number,author,closingIssuesReferences',
]);
if (prsJson === null) process.exit(1);
const prs = JSON.parse(prsJson);
console.log(`📊 Found ${prs.length} open PRs. Filtering...`);
let targetPrs = [];
for (const pr of prs) {
const author = pr.author.login;
const issueCount = pr.closingIssuesReferences
? pr.closingIssuesReferences.length
: 0;
if (issueCount > 0) {
// Skip if already linked to an issue
continue;
}
if (!isMaintainer(author)) {
targetPrs.push(pr);
}
}
console.log(
`✅ Found ${targetPrs.length} PRs from non-maintainers without associated issues.`,
);
const commentBody =
"\nHi @{AUTHOR}, thank you so much for your contribution to Gemini CLI! We really appreciate the time and effort you've put into this.\n\nWe're making some updates to our contribution process to improve how we track and review changes. Please take a moment to review our recent discussion post: [Improving Our Contribution Process & Introducing New Guidelines](${DISCUSSION_URL}).\n\nKey Update: Starting **January 26, 2026**, the Gemini CLI project will require all pull requests to be associated with an existing issue. Any pull requests not linked to an issue by that date will be automatically closed.\n\nThank you for your understanding and for being a part of our community!\n ".trim();
let successCount = 0;
let skipCount = 0;
let failCount = 0;
for (const pr of targetPrs) {
const prNumber = String(pr.number);
const author = pr.author.login;
// Check if we already commented (idempotency)
// We use silent: true here because view might fail if PR is deleted mid-run
const existingComments = runGh(
[
'pr',
'view',
prNumber,
'--repo',
REPO,
'--json',
'comments',
'--jq',
`.comments[].body | contains("${DISCUSSION_URL}")`,
],
{ silent: true },
);
if (existingComments && existingComments.includes('true')) {
console.log(
`⏭️ PR #${prNumber} already has the notification. Skipping.`,
);
skipCount++;
continue;
}
if (isDryRun) {
console.log(`[DRY RUN] Would notify @${author} on PR #${prNumber}`);
successCount++;
} else {
console.log(`💬 Notifying @${author} on PR #${prNumber}...`);
const personalizedComment = commentBody.replace('{AUTHOR}', author);
const result = runGh([
'pr',
'comment',
prNumber,
'--repo',
REPO,
'--body',
personalizedComment,
]);
if (result !== null) {
successCount++;
} else {
failCount++;
}
}
}
console.log(`\n📊 Summary:`);
console.log(` - Notified: ${successCount}`);
console.log(` - Skipped: ${skipCount}`);
console.log(` - Failed: ${failCount}`);
if (failCount > 0) process.exit(1);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
+100 -147
View File
@@ -1,180 +1,133 @@
#!/usr/bin/env bash
# @license
# Copyright 2026 Google LLC
# SPDX-License-Identifier: Apache-2.0
set -euo pipefail
# Initialize a comma-separated string to hold PR numbers that need a comment
PRS_NEEDING_COMMENT=""
# Global cache for issue labels (compatible with Bash 3.2)
# Stores "ISSUE_NUM:LABELS" pairs separated by spaces
ISSUE_LABELS_CACHE_FLAT=""
# Function to get area and priority labels from an issue (with caching)
get_issue_labels() {
local ISSUE_NUM="${1}"
if [[ -z "${ISSUE_NUM}" || "${ISSUE_NUM}" == "null" || "${ISSUE_NUM}" == "" ]]; then
return
# Function to process a single PR
process_pr() {
if [[ -z "${GITHUB_REPOSITORY:-}" ]]; then
echo "‼️ Missing \$GITHUB_REPOSITORY - this must be run from GitHub Actions"
return 1
fi
# Check cache
case " ${ISSUE_LABELS_CACHE_FLAT} " in
*" ${ISSUE_NUM}:"*)
local suffix="${ISSUE_LABELS_CACHE_FLAT#* " ${ISSUE_NUM}:"}"
echo "${suffix%% *}"
return
;;
*)
# Cache miss, proceed to fetch
;;
esac
echo " 📥 Fetching area and priority labels from issue #${ISSUE_NUM}" >&2
local gh_output
if ! gh_output=$(gh issue view "${ISSUE_NUM}" --repo "${GITHUB_REPOSITORY}" --json labels -q '.labels[].name' 2>/dev/null); then
echo " ⚠️ Could not fetch issue #${ISSUE_NUM}" >&2
ISSUE_LABELS_CACHE_FLAT="${ISSUE_LABELS_CACHE_FLAT} ${ISSUE_NUM}:"
return
if [[ -z "${GITHUB_OUTPUT:-}" ]]; then
echo "‼️ Missing \$GITHUB_OUTPUT - this must be run from GitHub Actions"
return 1
fi
local labels
labels=$(echo "${gh_output}" | grep -E '^(area|priority)/' | tr '\n' ',' | sed 's/,$//' || echo "")
# Save to flat cache
ISSUE_LABELS_CACHE_FLAT="${ISSUE_LABELS_CACHE_FLAT} ${ISSUE_NUM}:${labels}"
echo "${labels}"
}
# Function to process a single PR with pre-fetched data
process_pr_optimized() {
local PR_NUMBER="${1}"
local IS_DRAFT="${2}"
local ISSUE_NUMBER="${3}"
local CURRENT_LABELS="${4}" # Comma-separated labels
local PR_NUMBER=$1
echo "🔄 Processing PR #${PR_NUMBER}"
local LABELS_TO_ADD=""
local LABELS_TO_REMOVE=""
if [[ -z "${ISSUE_NUMBER}" || "${ISSUE_NUMBER}" == "null" || "${ISSUE_NUMBER}" == "" ]]; then
if [[ "${IS_DRAFT}" == "true" ]]; then
echo " 📝 PR #${PR_NUMBER} is a draft and has no linked issue"
if [[ ",${CURRENT_LABELS}," == *",status/need-issue,"* ]]; then
echo " Removing status/need-issue label"
LABELS_TO_REMOVE="status/need-issue"
fi
else
echo " ⚠️ No linked issue found for PR #${PR_NUMBER}"
if [[ ",${CURRENT_LABELS}," != *",status/need-issue,"* ]]; then
echo " Adding status/need-issue label"
LABELS_TO_ADD="status/need-issue"
fi
if [[ -z "${PRS_NEEDING_COMMENT}" ]]; then
PRS_NEEDING_COMMENT="${PR_NUMBER}"
else
PRS_NEEDING_COMMENT="${PRS_NEEDING_COMMENT},${PR_NUMBER}"
fi
fi
else
echo " 🔗 Found linked issue #${ISSUE_NUMBER}"
if [[ ",${CURRENT_LABELS}," == *",status/need-issue,"* ]]; then
echo " Removing status/need-issue label"
LABELS_TO_REMOVE="status/need-issue"
fi
local ISSUE_LABELS
ISSUE_LABELS=$(get_issue_labels "${ISSUE_NUMBER}")
if [[ -n "${ISSUE_LABELS}" ]]; then
local IFS_OLD="${IFS}"
IFS=','
for label in ${ISSUE_LABELS}; do
if [[ -n "${label}" ]] && [[ ",${CURRENT_LABELS}," != *",${label},"* ]]; then
if [[ -z "${LABELS_TO_ADD}" ]]; then
LABELS_TO_ADD="${label}"
else
LABELS_TO_ADD="${LABELS_TO_ADD},${label}"
fi
fi
done
IFS="${IFS_OLD}"
fi
if [[ -z "${LABELS_TO_ADD}" && -z "${LABELS_TO_REMOVE}" ]]; then
echo " ✅ Labels already synchronized"
fi
# Get closing issue number with error handling
local ISSUE_NUMBER
if ! ISSUE_NUMBER=$(gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json closingIssuesReferences -q '.closingIssuesReferences.nodes[0].number' 2>/dev/null); then
echo " ⚠️ Could not fetch closing issue for PR #${PR_NUMBER}"
fi
if [[ -n "${LABELS_TO_ADD}" || -n "${LABELS_TO_REMOVE}" ]]; then
local EDIT_CMD=("gh" "pr" "edit" "${PR_NUMBER}" "--repo" "${GITHUB_REPOSITORY}")
if [[ -z "${ISSUE_NUMBER}" ]]; then
echo "⚠️ No linked issue found for PR #${PR_NUMBER}, adding status/need-issue label"
if ! gh pr edit "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --add-label "status/need-issue" 2>/dev/null; then
echo " ⚠️ Failed to add label (may already exist or have permission issues)"
fi
# Add PR number to the list
if [[ -z "${PRS_NEEDING_COMMENT}" ]]; then
PRS_NEEDING_COMMENT="${PR_NUMBER}"
else
PRS_NEEDING_COMMENT="${PRS_NEEDING_COMMENT},${PR_NUMBER}"
fi
echo "needs_comment=true" >> "${GITHUB_OUTPUT}"
else
echo "🔗 Found linked issue #${ISSUE_NUMBER}"
# Remove status/need-issue label if present
if ! gh pr edit "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --remove-label "status/need-issue" 2>/dev/null; then
echo " status/need-issue label not present or could not be removed"
fi
# Get issue labels
echo "📥 Fetching labels from issue #${ISSUE_NUMBER}"
local ISSUE_LABELS=""
if ! ISSUE_LABELS=$(gh issue view "${ISSUE_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json labels -q '.labels[].name' 2>/dev/null | tr '\n' ',' | sed 's/,$//' || echo ""); then
echo " ⚠️ Could not fetch issue #${ISSUE_NUMBER} (may not exist or be in different repo)"
ISSUE_LABELS=""
fi
# Get PR labels
echo "📥 Fetching labels from PR #${PR_NUMBER}"
local PR_LABELS=""
if ! PR_LABELS=$(gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json labels -q '.labels[].name' 2>/dev/null | tr '\n' ',' | sed 's/,$//' || echo ""); then
echo " ⚠️ Could not fetch PR labels"
PR_LABELS=""
fi
echo " Issue labels: ${ISSUE_LABELS}"
echo " PR labels: ${PR_LABELS}"
# Convert comma-separated strings to arrays
local ISSUE_LABEL_ARRAY PR_LABEL_ARRAY
IFS=',' read -ra ISSUE_LABEL_ARRAY <<< "${ISSUE_LABELS}"
IFS=',' read -ra PR_LABEL_ARRAY <<< "${PR_LABELS}"
# Find labels to add (on issue but not on PR)
local LABELS_TO_ADD=""
for label in "${ISSUE_LABEL_ARRAY[@]}"; do
if [[ -n "${label}" ]] && [[ " ${PR_LABEL_ARRAY[*]} " != *" ${label} "* ]]; then
if [[ -z "${LABELS_TO_ADD}" ]]; then
LABELS_TO_ADD="${label}"
else
LABELS_TO_ADD="${LABELS_TO_ADD},${label}"
fi
fi
done
# Apply label changes
if [[ -n "${LABELS_TO_ADD}" ]]; then
echo " Syncing labels to add: ${LABELS_TO_ADD}"
EDIT_CMD+=("--add-label" "${LABELS_TO_ADD}")
echo " Adding labels: ${LABELS_TO_ADD}"
if ! gh pr edit "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --add-label "${LABELS_TO_ADD}" 2>/dev/null; then
echo " ⚠️ Failed to add some labels"
fi
fi
if [[ -n "${LABELS_TO_REMOVE}" ]]; then
echo " Syncing labels to remove: ${LABELS_TO_REMOVE}"
EDIT_CMD+=("--remove-label" "${LABELS_TO_REMOVE}")
if [[ -z "${LABELS_TO_ADD}" ]]; then
echo "✅ Labels already synchronized"
fi
("${EDIT_CMD[@]}" 2>/dev/null || true)
echo "needs_comment=false" >> "${GITHUB_OUTPUT}"
fi
}
if [[ -z "${GITHUB_REPOSITORY:-}" ]]; then
echo "‼️ Missing \$GITHUB_REPOSITORY - this must be run from GitHub Actions"
exit 1
fi
if [[ -z "${GITHUB_OUTPUT:-}" ]]; then
echo "‼️ Missing \$GITHUB_OUTPUT - this must be run from GitHub Actions"
exit 1
fi
JQ_EXTRACT_FIELDS='{
number: .number,
isDraft: .isDraft,
issue: (.closingIssuesReferences[0].number // (.body // "" | capture("(^|[^a-zA-Z0-9])#(?<num>[0-9]+)([^a-zA-Z0-9]|$)")? | .num) // "null"),
labels: [.labels[].name] | join(",")
}'
JQ_TSV_FORMAT='"\((.number | tostring))\t\(.isDraft)\t\((.issue // null) | tostring)\t\(.labels)"'
# If PR_NUMBER is set, process only that PR
if [[ -n "${PR_NUMBER:-}" ]]; then
echo "🔄 Processing single PR #${PR_NUMBER}"
PR_DATA=$(gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json number,closingIssuesReferences,isDraft,body,labels 2>/dev/null) || {
echo "❌ Failed to fetch data for PR #${PR_NUMBER}"
if ! process_pr "${PR_NUMBER}"; then
echo "❌ Failed to process PR #${PR_NUMBER}"
exit 1
}
line=$(echo "${PR_DATA}" | jq -r "${JQ_EXTRACT_FIELDS} | ${JQ_TSV_FORMAT}")
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "${line}"
process_pr_optimized "${pr_num}" "${is_draft}" "${issue_num}" "${current_labels}"
fi
else
# Otherwise, get all open PRs and process them
# The script logic will determine which ones need issue linking or label sync
echo "📥 Getting all open pull requests..."
PR_DATA_ALL=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state open --limit 1000 --json number,closingIssuesReferences,isDraft,body,labels 2>/dev/null) || {
if ! PR_NUMBERS=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state open --limit 1000 --json number -q '.[].number' 2>/dev/null); then
echo "❌ Failed to fetch PR list"
exit 1
}
fi
PR_COUNT=$(echo "${PR_DATA_ALL}" | jq '. | length')
echo "📊 Found ${PR_COUNT} open PRs to process"
if [[ -z "${PR_NUMBERS}" ]]; then
echo "✅ No open PRs found"
else
# Count the number of PRs
PR_COUNT=$(echo "${PR_NUMBERS}" | wc -w | tr -d ' ')
echo "📊 Found ${PR_COUNT} open PRs to process"
# Use a temporary file to avoid masking exit codes in process substitution
tmp_file=$(mktemp)
echo "${PR_DATA_ALL}" | jq -r ".[] | ${JQ_EXTRACT_FIELDS} | ${JQ_TSV_FORMAT}" > "${tmp_file}"
while read -r line; do
[[ -z "${line}" ]] && continue
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "${line}"
process_pr_optimized "${pr_num}" "${is_draft}" "${issue_num}" "${current_labels}"
done < "${tmp_file}"
rm -f "${tmp_file}"
for pr_number in ${PR_NUMBERS}; do
if ! process_pr "${pr_number}"; then
echo "⚠️ Failed to process PR #${pr_number}, continuing with next PR..."
continue
fi
done
fi
fi
# Ensure output is always set, even if empty
if [[ -z "${PRS_NEEDING_COMMENT}" ]]; then
echo "prs_needing_comment=[]" >> "${GITHUB_OUTPUT}"
else
-355
View File
@@ -1,355 +0,0 @@
/* eslint-disable @typescript-eslint/no-require-imports */
/* global process, console, require */
const { Octokit } = require('@octokit/rest');
/**
* Sync Maintainer Labels (Recursive with strict parent-child relationship detection)
* - Uses Native Sub-issues.
* - Uses Markdown Task Lists (- [ ] #123).
* - Filters for OPEN issues only.
* - Skips DUPLICATES.
* - Skips Pull Requests.
* - ONLY labels issues in the PUBLIC (gemini-cli) repo.
*/
const REPO_OWNER = 'google-gemini';
const PUBLIC_REPO = 'gemini-cli';
const PRIVATE_REPO = 'maintainers-gemini-cli';
const ALLOWED_REPOS = [PUBLIC_REPO, PRIVATE_REPO];
const ROOT_ISSUES = [
{ owner: REPO_OWNER, repo: PUBLIC_REPO, number: 15374 },
{ owner: REPO_OWNER, repo: PUBLIC_REPO, number: 15456 },
{ owner: REPO_OWNER, repo: PUBLIC_REPO, number: 15324 },
];
const TARGET_LABEL = '🔒 maintainer only';
const isDryRun =
process.argv.includes('--dry-run') || process.env.DRY_RUN === 'true';
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
/**
* Extracts child issue references from markdown Task Lists ONLY.
* e.g. - [ ] #123 or - [x] google-gemini/gemini-cli#123
*/
function extractTaskListLinks(text, contextOwner, contextRepo) {
if (!text) return [];
const childIssues = new Map();
const add = (owner, repo, number) => {
if (ALLOWED_REPOS.includes(repo)) {
const key = `${owner}/${repo}#${number}`;
childIssues.set(key, { owner, repo, number: parseInt(number, 10) });
}
};
// 1. Full URLs in task lists
const urlRegex =
/-\s+\[[ x]\].*https:\/\/github\.com\/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)\/issues\/(\d+)\b/g;
let match;
while ((match = urlRegex.exec(text)) !== null) {
add(match[1], match[2], match[3]);
}
// 2. Cross-repo refs in task lists: owner/repo#123
const crossRepoRegex =
/-\s+\[[ x]\].*([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)#(\d+)\b/g;
while ((match = crossRepoRegex.exec(text)) !== null) {
add(match[1], match[2], match[3]);
}
// 3. Short refs in task lists: #123
const shortRefRegex = /-\s+\[[ x]\].*#(\d+)\b/g;
while ((match = shortRefRegex.exec(text)) !== null) {
add(contextOwner, contextRepo, match[1]);
}
return Array.from(childIssues.values());
}
/**
* Fetches issue data via GraphQL with full pagination for sub-issues, comments, and labels.
*/
async function fetchIssueData(owner, repo, number) {
const query = `
query($owner:String!, $repo:String!, $number:Int!) {
repository(owner:$owner, name:$repo) {
issue(number:$number) {
state
title
body
labels(first: 100) {
nodes { name }
pageInfo { hasNextPage endCursor }
}
subIssues(first: 100) {
nodes {
number
repository {
name
owner { login }
}
}
pageInfo { hasNextPage endCursor }
}
comments(first: 100) {
nodes {
body
}
}
}
}
}
`;
try {
const response = await octokit.graphql(query, { owner, repo, number });
const data = response.repository.issue;
if (!data) return null;
const issue = {
state: data.state,
title: data.title,
body: data.body || '',
labels: data.labels.nodes.map((n) => n.name),
subIssues: [...data.subIssues.nodes],
comments: data.comments.nodes.map((n) => n.body),
};
// Paginate subIssues if there are more than 100
if (data.subIssues.pageInfo.hasNextPage) {
const moreSubIssues = await paginateConnection(
owner,
repo,
number,
'subIssues',
'number repository { name owner { login } }',
data.subIssues.pageInfo.endCursor,
);
issue.subIssues.push(...moreSubIssues);
}
// Paginate labels if there are more than 100 (unlikely but for completeness)
if (data.labels.pageInfo.hasNextPage) {
const moreLabels = await paginateConnection(
owner,
repo,
number,
'labels',
'name',
data.labels.pageInfo.endCursor,
(n) => n.name,
);
issue.labels.push(...moreLabels);
}
// Note: Comments are handled via Task Lists in body + first 100 comments.
// If an issue has > 100 comments with task lists, we'd need to paginate those too.
// Given the 1,100+ issue discovery count, 100 comments is usually sufficient,
// but we can add it for absolute completeness.
// (Skipping for now to avoid excessive API churn unless clearly needed).
return issue;
} catch (error) {
if (error.errors && error.errors.some((e) => e.type === 'NOT_FOUND')) {
return null;
}
throw error;
}
}
/**
* Helper to paginate any GraphQL connection.
*/
async function paginateConnection(
owner,
repo,
number,
connectionName,
nodeFields,
initialCursor,
transformNode = (n) => n,
) {
let additionalNodes = [];
let hasNext = true;
let cursor = initialCursor;
while (hasNext) {
const query = `
query($owner:String!, $repo:String!, $number:Int!, $cursor:String) {
repository(owner:$owner, name:$repo) {
issue(number:$number) {
${connectionName}(first: 100, after: $cursor) {
nodes { ${nodeFields} }
pageInfo { hasNextPage endCursor }
}
}
}
}
`;
const response = await octokit.graphql(query, {
owner,
repo,
number,
cursor,
});
const connection = response.repository.issue[connectionName];
additionalNodes.push(...connection.nodes.map(transformNode));
hasNext = connection.pageInfo.hasNextPage;
cursor = connection.pageInfo.endCursor;
}
return additionalNodes;
}
/**
* Validates if an issue should be processed (Open, not a duplicate, not a PR)
*/
function shouldProcess(issueData) {
if (!issueData) return false;
if (issueData.state !== 'OPEN') return false;
const labels = issueData.labels.map((l) => l.toLowerCase());
if (labels.includes('duplicate') || labels.includes('kind/duplicate')) {
return false;
}
return true;
}
async function getAllDescendants(roots) {
const allDescendants = new Map();
const visited = new Set();
const queue = [...roots];
for (const root of roots) {
visited.add(`${root.owner}/${root.repo}#${root.number}`);
}
console.log(`Starting discovery from ${roots.length} roots...`);
while (queue.length > 0) {
const current = queue.shift();
const currentKey = `${current.owner}/${current.repo}#${current.number}`;
try {
const issueData = await fetchIssueData(
current.owner,
current.repo,
current.number,
);
if (!shouldProcess(issueData)) {
continue;
}
// ONLY add to labeling list if it's in the PUBLIC repository
if (current.repo === PUBLIC_REPO) {
// Don't label the roots themselves
if (
!ROOT_ISSUES.some(
(r) => r.number === current.number && r.repo === current.repo,
)
) {
allDescendants.set(currentKey, {
...current,
title: issueData.title,
labels: issueData.labels,
});
}
}
const children = new Map();
// 1. Process Native Sub-issues
if (issueData.subIssues) {
for (const node of issueData.subIssues) {
const childOwner = node.repository.owner.login;
const childRepo = node.repository.name;
const childNumber = node.number;
const key = `${childOwner}/${childRepo}#${childNumber}`;
children.set(key, {
owner: childOwner,
repo: childRepo,
number: childNumber,
});
}
}
// 2. Process Markdown Task Lists in Body and Comments
let combinedText = issueData.body || '';
if (issueData.comments) {
for (const commentBody of issueData.comments) {
combinedText += '\n' + (commentBody || '');
}
}
const taskListLinks = extractTaskListLinks(
combinedText,
current.owner,
current.repo,
);
for (const link of taskListLinks) {
const key = `${link.owner}/${link.repo}#${link.number}`;
children.set(key, link);
}
// Queue children (regardless of which repo they are in, for recursion)
for (const [key, child] of children) {
if (!visited.has(key)) {
visited.add(key);
queue.push(child);
}
}
} catch (error) {
console.error(`Error processing ${currentKey}: ${error.message}`);
}
}
return Array.from(allDescendants.values());
}
async function run() {
if (isDryRun) {
console.log('=== DRY RUN MODE: No labels will be applied ===');
}
const descendants = await getAllDescendants(ROOT_ISSUES);
console.log(
`\nFound ${descendants.length} total unique open descendant issues in ${PUBLIC_REPO}.`,
);
for (const issueInfo of descendants) {
const issueKey = `${issueInfo.owner}/${issueInfo.repo}#${issueInfo.number}`;
try {
// Data is already available from the discovery phase
const hasLabel = issueInfo.labels.some((l) => l === TARGET_LABEL);
if (!hasLabel) {
if (isDryRun) {
console.log(
`[DRY RUN] Would label ${issueKey}: "${issueInfo.title}"`,
);
} else {
console.log(`Labeling ${issueKey}: "${issueInfo.title}"...`);
await octokit.rest.issues.addLabels({
owner: issueInfo.owner,
repo: issueInfo.repo,
issue_number: issueInfo.number,
labels: [TARGET_LABEL],
});
}
}
} catch (error) {
console.error(`Error processing label for ${issueKey}: ${error.message}`);
}
}
}
run().catch((error) => {
console.error(error);
process.exit(1);
});
+1 -35
View File
@@ -168,7 +168,6 @@ jobs:
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
KEEP_OUTPUT: 'true'
VERBOSE: 'true'
BUILD_SANDBOX_FLAGS: '--cache-from type=gha --cache-to type=gha,mode=max'
shell: 'bash'
run: |
if [[ "${{ matrix.sandbox }}" == "sandbox:docker" ]]; then
@@ -277,37 +276,6 @@ jobs:
shell: 'pwsh'
run: 'npm run test:integration:sandbox:none'
evals:
name: 'Evals (ALWAYS_PASSING)'
needs:
- 'merge_queue_skipper'
- 'parse_run_context'
runs-on: 'gemini-cli-ubuntu-16-core'
if: |
always() && (needs.merge_queue_skipper.result !='success' || needs.merge_queue_skipper.outputs.skip != 'true')
steps:
- name: 'Checkout'
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
with:
ref: '${{ needs.parse_run_context.outputs.sha }}'
repository: '${{ needs.parse_run_context.outputs.repository }}'
- name: 'Set up Node.js 20.x'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
with:
node-version: '20.x'
- name: 'Install dependencies'
run: 'npm ci'
- name: 'Build project'
run: 'npm run build'
- name: 'Run Evals (Required to pass)'
env:
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
run: 'npm run test:always_passing_evals'
e2e:
name: 'E2E'
if: |
@@ -315,15 +283,13 @@ jobs:
needs:
- 'e2e_linux'
- 'e2e_mac'
- 'evals'
- 'merge_queue_skipper'
runs-on: 'gemini-cli-ubuntu-16-core'
steps:
- name: 'Check E2E test results'
run: |
if [[ ${{ needs.e2e_linux.result }} != 'success' || \
${{ needs.e2e_mac.result }} != 'success' || \
${{ needs.evals.result }} != 'success' ]]; then
${{ needs.e2e_mac.result }} != 'success' ]]; then
echo "One or more E2E jobs failed."
exit 1
fi
+15 -45
View File
@@ -43,15 +43,13 @@ jobs:
- id: 'merge-queue-ci-skipper'
uses: 'cariad-tech/merge-queue-ci-skipper@1032489e59437862c90a08a2c92809c903883772' # ratchet:cariad-tech/merge-queue-ci-skipper@main
with:
secret: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
secret: '${{ secrets.GITHUB_TOKEN }}'
lint:
name: 'Lint'
runs-on: 'gemini-cli-ubuntu-16-core'
needs: 'merge_queue_skipper'
if: "${{needs.merge_queue_skipper.outputs.skip == 'false'}}"
env:
GEMINI_LINT_TEMP_DIR: '${{ github.workspace }}/.gemini-linters'
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
@@ -65,21 +63,9 @@ jobs:
node-version-file: '.nvmrc'
cache: 'npm'
- name: 'Cache Linters'
uses: 'actions/cache@v4'
with:
path: '${{ env.GEMINI_LINT_TEMP_DIR }}'
key: "${{ runner.os }}-${{ runner.arch }}-linters-${{ hashFiles('scripts/lint.js') }}"
- name: 'Install dependencies'
run: 'npm ci'
- name: 'Cache ESLint'
uses: 'actions/cache@v4'
with:
path: '.eslintcache'
key: "${{ runner.os }}-eslint-${{ hashFiles('package-lock.json', 'eslint.config.js') }}"
- name: 'Validate NOTICES.txt'
run: 'git diff --exit-code packages/vscode-ide-companion/NOTICES.txt'
@@ -125,9 +111,10 @@ jobs:
args: '--verbose --accept 200,503 ./**/*.md'
fail: true
test_linux:
name: 'Test (Linux) - ${{ matrix.node-version }}, ${{ matrix.shard }}'
name: 'Test (Linux)'
runs-on: 'gemini-cli-ubuntu-16-core'
needs:
- 'lint'
- 'merge_queue_skipper'
if: "${{needs.merge_queue_skipper.outputs.skip == 'false'}}"
permissions:
@@ -140,9 +127,6 @@ jobs:
- '20.x'
- '22.x'
- '24.x'
shard:
- 'cli'
- 'others'
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
@@ -162,14 +146,7 @@ jobs:
- name: 'Run tests and generate reports'
env:
NO_COLOR: true
run: |
if [[ "${{ matrix.shard }}" == "cli" ]]; then
npm run test:ci --workspace @google/gemini-cli
else
# Explicitly list non-cli packages to ensure they are sharded correctly
npm run test:ci --workspace @google/gemini-cli-core --workspace @google/gemini-cli-a2a-server --workspace gemini-cli-vscode-ide-companion --workspace @google/gemini-cli-test-utils --if-present
npm run test:scripts
fi
run: 'npm run test:ci'
- name: 'Bundle'
run: 'npm run bundle'
@@ -185,7 +162,7 @@ jobs:
${{ always() && (github.event.pull_request.head.repo.full_name == github.repository) }}
uses: 'dorny/test-reporter@dc3a92680fcc15842eef52e8c4606ea7ce6bd3f3' # ratchet:dorny/test-reporter@v2
with:
name: 'Test Results (Node ${{ runner.os }}, ${{ matrix.node-version }}, ${{ matrix.shard }})'
name: 'Test Results (Node ${{ matrix.node-version }})'
path: 'packages/*/junit.xml'
reporter: 'java-junit'
fail-on-error: 'false'
@@ -195,13 +172,14 @@ jobs:
${{ always() && (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }}
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
with:
name: 'test-results-fork-${{ runner.os }}-${{ matrix.node-version }}-${{ matrix.shard }}'
name: 'test-results-fork-${{ matrix.node-version }}-${{ runner.os }}'
path: 'packages/*/junit.xml'
test_mac:
name: 'Test (Mac) - ${{ matrix.node-version }}, ${{ matrix.shard }}'
runs-on: 'macos-latest'
name: 'Test (Mac)'
runs-on: '${{ matrix.os }}'
needs:
- 'lint'
- 'merge_queue_skipper'
if: "${{needs.merge_queue_skipper.outputs.skip == 'false'}}"
permissions:
@@ -211,13 +189,12 @@ jobs:
continue-on-error: true
strategy:
matrix:
os:
- 'macos-latest'
node-version:
- '20.x'
- '22.x'
- '24.x'
shard:
- 'cli'
- 'others'
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
@@ -237,14 +214,7 @@ jobs:
- name: 'Run tests and generate reports'
env:
NO_COLOR: true
run: |
if [[ "${{ matrix.shard }}" == "cli" ]]; then
npm run test:ci --workspace @google/gemini-cli -- --coverage.enabled=false
else
# Explicitly list non-cli packages to ensure they are sharded correctly
npm run test:ci --workspace @google/gemini-cli-core --workspace @google/gemini-cli-a2a-server --workspace gemini-cli-vscode-ide-companion --workspace @google/gemini-cli-test-utils --if-present -- --coverage.enabled=false
npm run test:scripts
fi
run: 'npm run test:ci -- --coverage.enabled=false'
- name: 'Bundle'
run: 'npm run bundle'
@@ -260,7 +230,7 @@ jobs:
${{ always() && (github.event.pull_request.head.repo.full_name == github.repository) }}
uses: 'dorny/test-reporter@dc3a92680fcc15842eef52e8c4606ea7ce6bd3f3' # ratchet:dorny/test-reporter@v2
with:
name: 'Test Results (Node ${{ runner.os }}, ${{ matrix.node-version }}, ${{ matrix.shard }})'
name: 'Test Results (Node ${{ matrix.node-version }})'
path: 'packages/*/junit.xml'
reporter: 'java-junit'
fail-on-error: 'false'
@@ -270,7 +240,7 @@ jobs:
${{ always() && (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }}
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
with:
name: 'test-results-fork-${{ runner.os }}-${{ matrix.node-version }}-${{ matrix.shard }}'
name: 'test-results-fork-${{ matrix.node-version }}-${{ runner.os }}'
path: 'packages/*/junit.xml'
- name: 'Upload coverage reports'
@@ -278,7 +248,7 @@ jobs:
${{ always() }}
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
with:
name: 'coverage-reports-${{ runner.os }}-${{ matrix.node-version }}-${{ matrix.shard }}'
name: 'coverage-reports-${{ matrix.node-version }}-${{ matrix.os }}'
path: 'packages/*/coverage'
codeql:
-76
View File
@@ -1,76 +0,0 @@
name: 'Evals: Nightly'
on:
schedule:
- cron: '0 1 * * *' # Runs at 1 AM every day
workflow_dispatch:
inputs:
run_all:
description: 'Run all evaluations (including usually passing)'
type: 'boolean'
default: true
permissions:
contents: 'read'
checks: 'write'
actions: 'read'
jobs:
evals:
name: 'Evals (USUALLY_PASSING) nightly run'
runs-on: 'gemini-cli-ubuntu-16-core'
strategy:
fail-fast: false
matrix:
run_attempt: [1, 2, 3]
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
- name: 'Set up Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: 'Install dependencies'
run: 'npm ci'
- name: 'Build project'
run: 'npm run build'
- name: 'Create logs directory'
run: 'mkdir -p evals/logs'
- name: 'Run Evals'
env:
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
RUN_EVALS: "${{ github.event.inputs.run_all != 'false' }}"
run: 'npm run test:all_evals'
- name: 'Upload Logs'
if: 'always()'
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
with:
name: 'eval-logs-${{ matrix.run_attempt }}'
path: 'evals/logs'
retention-days: 7
aggregate-results:
name: 'Aggregate Results'
needs: ['evals']
if: 'always()'
runs-on: 'gemini-cli-ubuntu-16-core'
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
- name: 'Download Logs'
uses: 'actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806' # ratchet:actions/download-artifact@v4
with:
path: 'artifacts'
- name: 'Generate Summary'
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
run: 'node scripts/aggregate_evals.js artifacts >> "$GITHUB_STEP_SUMMARY"'
@@ -101,6 +101,7 @@ jobs:
"FIRESTORE_DATABASE_ID": "(default)",
"GOOGLE_APPLICATION_CREDENTIALS": "${GOOGLE_APPLICATION_CREDENTIALS}"
},
"enabled": true,
"timeout": 600000
}
},
@@ -14,15 +14,9 @@ on:
description: 'issue number to triage'
required: true
type: 'number'
workflow_call:
inputs:
issue_number:
description: 'issue number to triage'
required: false
type: 'string'
concurrency:
group: '${{ github.workflow }}-${{ github.event.issue.number || github.event.inputs.issue_number || inputs.issue_number }}'
group: '${{ github.workflow }}-${{ github.event.issue.number || github.event.inputs.issue_number }}'
cancel-in-progress: true
defaults:
@@ -40,11 +34,12 @@ permissions:
jobs:
triage-issue:
if: |-
(github.repository == 'google-gemini/gemini-cli' || github.repository == 'google-gemini/maintainers-gemini-cli') &&
github.repository == 'google-gemini/gemini-cli' &&
(
github.event_name == 'workflow_dispatch' ||
(
(github.event_name == 'issues' || github.event_name == 'issue_comment') &&
contains(github.event.issue.labels.*.name, 'status/need-triage') &&
(github.event_name != 'issue_comment' || (
contains(github.event.comment.body, '@gemini-cli /triage') &&
(github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR')
@@ -63,11 +58,10 @@ jobs:
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'
script: |
const issueNumber = ${{ github.event.inputs.issue_number || inputs.issue_number }};
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
issue_number: ${{ github.event.inputs.issue_number }},
});
core.setOutput('title', issue.title);
core.setOutput('body', issue.body);
@@ -78,9 +72,14 @@ jobs:
if: |-
github.event_name == 'workflow_dispatch'
env:
ISSUE_NUMBER_INPUT: '${{ github.event.inputs.issue_number || inputs.issue_number }}'
ISSUE_NUMBER_INPUT: '${{ github.event.inputs.issue_number }}'
LABELS: '${{ steps.get_issue_data.outputs.labels }}'
run: |
if ! echo "${LABELS}" | grep -q 'status/need-triage'; then
echo "Issue #${ISSUE_NUMBER_INPUT} does not have the 'status/need-triage' label. Stopping workflow."
exit 1
fi
if echo "${LABELS}" | grep -q 'area/'; then
echo "Issue #${ISSUE_NUMBER_INPUT} already has 'area/' label. Stopping workflow."
exit 1
@@ -93,10 +92,6 @@ jobs:
- name: 'Generate GitHub App Token'
id: 'generate_token'
env:
APP_ID: '${{ secrets.APP_ID }}'
if: |-
${{ env.APP_ID != '' }}
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
with:
app-id: '${{ secrets.APP_ID }}'
@@ -107,7 +102,7 @@ jobs:
id: 'get_labels'
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
with:
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
github-token: '${{ steps.generate_token.outputs.token }}'
script: |-
const { data: labels } = await github.rest.issues.listLabelsForRepo({
owner: context.repo.owner,
@@ -138,7 +133,7 @@ jobs:
ISSUE_BODY: >-
${{ github.event_name == 'workflow_dispatch' && steps.get_issue_data.outputs.body || github.event.issue.body }}
ISSUE_NUMBER: >-
${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.issue_number || inputs.issue_number) || github.event.issue.number }}
${{ github.event_name == 'workflow_dispatch' && github.event.inputs.issue_number || github.event.issue.number }}
REPOSITORY: '${{ github.repository }}'
AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}'
with:
@@ -264,7 +259,7 @@ jobs:
LABELS_OUTPUT: '${{ steps.gemini_issue_analysis.outputs.summary }}'
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
with:
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
github-token: '${{ steps.generate_token.outputs.token }}'
script: |
const rawOutput = process.env.LABELS_OUTPUT;
core.info(`Raw output from model: ${rawOutput}`);
@@ -306,6 +301,22 @@ jobs:
});
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}`);
// Remove the 'status/need-triage' label
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name: 'status/need-triage'
});
core.info(`Successfully removed 'status/need-triage' label.`);
} catch (error) {
// If the label doesn't exist, the API call will throw a 404. We can ignore this.
if (error.status !== 404) {
core.warning(`Failed to remove 'status/need-triage': ${error.message}`);
}
}
- name: 'Post Issue Analysis Failure Comment'
if: |-
${{ failure() && steps.gemini_issue_analysis.outcome == 'failure' }}
@@ -314,7 +325,7 @@ jobs:
RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
with:
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
github-token: '${{ steps.generate_token.outputs.token }}'
script: |-
github.rest.issues.createComment({
owner: context.repo.owner,
@@ -0,0 +1,127 @@
name: 'Gemini Automated PR Labeler'
on:
pull_request_target:
types: ['opened', 'reopened', 'synchronize']
jobs:
label-pr:
timeout-minutes: 10
if: |-
${{ github.repository == 'google-gemini/gemini-cli' }}
permissions:
pull-requests: 'write'
contents: 'read'
id-token: 'write'
concurrency:
group: '${{ github.workflow }}-${{ github.event.pull_request.number }}'
cancel-in-progress: true
runs-on: 'ubuntu-latest'
steps:
- name: 'Generate GitHub App Token'
id: 'generate_token'
uses: 'actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e' # v2
with:
app-id: '${{ secrets.APP_ID }}'
private-key: '${{ secrets.PRIVATE_KEY }}'
permission-pull-requests: 'write'
- name: 'Run Gemini PR size and complexity labeller'
uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # Use the specific commit SHA
env:
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}'
PR_NUMBER: '${{ github.event.pull_request.number }}'
REPOSITORY: '${{ github.repository }}'
with:
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}'
gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}'
gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}'
use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}'
settings: |
{
"coreTools": [
"run_shell_command(gh pr diff)",
"run_shell_command(gh pr edit)",
"run_shell_command(gh pr comment)",
"run_shell_command(gh pr view)"
],
"telemetry": {
"enabled": true,
"target": "gcp"
},
"sandbox": false
}
prompt: |
You are a Pull Request labeller and Feedback Assistant. Your primary goal is to improve review velocity and help maintainers prioritize their work by automatically labeling pull requests based on size and complexity, and providing guidance for overly large PRs.
Steps:
1. Retrieve Pull Request Information:
- Use `gh pr diff ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }}` to get the diff content.
- Parse the output from `gh pr diff` to determine the total lines of code added and deleted. Calculate `TOTAL_LINES_CHANGED`.
2. Determine Pull Request Size:
- Use `gh pr view ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }} --json labels` to get the current labels on the PR.
- Check the current labels and identify if any `size/*` labels already exist (e.g., `size/xs`, `size/s`, etc.).
- If an old `size/*` label is found and it is different from the newly calculated size, remove it using:
`gh pr edit ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }} --remove-label "size-label-to-remove"`
- Based on `TOTAL_LINES_CHANGED`, select the appropriate new size label:
- `size/xs`: < 10 lines changed
- `size/s`: 10-50 lines changed
- `size/m`: 51-200 lines changed
- `size/l`: 201-1000 lines changed
- `size/xl`: > 1000 lines changed
- Do not invent new size labels.
- Apply the newly determined `size/*` label to the pull request using:
`gh pr edit ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }} --add-label "your-new-size-label"`
3. Analyze Pull Request Complexity:
- Perform Code Change Analysis: Examine the content of the code changes obtained from `gh pr diff ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }}`. Look for indicators of complexity such as:
- Number of files changed (can be inferred from the diff headers).
- Diversity of file types (e.g., changes across different languages, configuration files, documentation).
- Presence of new external dependencies.
- Introduction of new architectural components or significant refactoring.
- Complexity of individual code changes (e.g., deeply nested logic, complex algorithms, extensive conditional statements).
- Apply Heuristic-based Complexity Assessment:
- If the PR touches a small number of files with minor changes (e.g., typos, simple bug fixes, small feature additions), categorize it as `review/quick`.
- If the PR involves changes across multiple files, introduces new features, significantly refactors existing code, or has a high line count (even within `size/l`), categorize it as `review/involved`.
- Pay close attention to changes in critical or core modules as these inherently increase complexity.
- **Only use the labels `review/quick` or `review/involved` for complexity. Do not invent new complexity labels.**
- **Remove any previous `review/*` labels if they no longer apply, similar to the size label process.**
- Apply the determined `review/*` label to the pull request using:
`gh pr edit ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }} --add-label "your-complexity-label"`
4. Handle Overly Large Pull Requests (`size/xl`):
- **Conditional Check:** If the pull request has been labeled `size/xl` (i.e., > 1000 lines of code changed) in Step 2, proceed to the next sub-step.
- **Post Constructive Comment:** Post a polite and helpful comment on the pull request using:
`gh pr comment ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }} --body "Your comment here"`
The comment body should be:
"""
Thanks for your hard work on this pull request!
This pull request is quite large, which can make it challenging and time-consuming for reviewers to go through thoroughly.
To help us review it more efficiently and get your changes merged faster, we kindly request you consider breaking this into smaller, more focused pull requests. Each smaller PR should ideally address a single logical change or a small set of related changes.
For example, you could separate out refactoring, new feature additions, and bug fixes into individual PRs. This makes it easier to understand, review, and test each component independently.
We appreciate your understanding and cooperation. Feel free to reach out if you need any assistance with this!
"""
Guidelines:
- Automation Focus: All actions should be automated and not require manual intervention.
- Non-intrusive: The system should add labels and comments but not modify the code or close the pull request.
- Polite and Constructive: All communication, especially for large PRs, must be polite, encouraging, and constructive.
- Prioritize Clarity: The labels applied should clearly convey the PR's size and complexity to reviewers.
- Adhere to Defined Labels: Only use the specified `size/*` and `review/*` labels. Do not create or apply any other labels.
- Utilize `gh CLI`: Interact with GitHub using the `gh` command-line tool for diffing, label management, and commenting.
- Execute commands strictly as described in the steps. Do not invent new commands.
- In no case should you change other pull request that are not the one you are working on. Which can be found by using env.PR_NUMBER
- Execute each step that is defined in the steps section.
- In no case should you execute code from the pull request because this could be malicious code.
- If you fail to do this step log the errors you received
@@ -81,6 +81,7 @@ jobs:
"FIRESTORE_DATABASE_ID": "(default)",
"GOOGLE_APPLICATION_CREDENTIALS": "${GOOGLE_APPLICATION_CREDENTIALS}"
},
"enabled": true,
"timeout": 600000
}
},
@@ -1,16 +1,12 @@
name: '📋 Gemini Scheduled Issue Triage'
on:
issues:
types:
- 'opened'
- 'reopened'
schedule:
- cron: '0 * * * *' # Runs every hour
workflow_dispatch:
concurrency:
group: '${{ github.workflow }}-${{ github.event.number || github.run_id }}'
group: '${{ github.workflow }}'
cancel-in-progress: true
defaults:
@@ -39,21 +35,7 @@ jobs:
private-key: '${{ secrets.PRIVATE_KEY }}'
permission-issues: 'write'
- name: 'Get issue from event'
if: |-
${{ github.event_name == 'issues' }}
id: 'get_issue_from_event'
env:
ISSUE_EVENT: '${{ toJSON(github.event.issue) }}'
run: |
set -euo pipefail
ISSUE_JSON=$(echo "$ISSUE_EVENT" | jq -c '[{number: .number, title: .title, body: .body}]')
echo "issues_to_triage=${ISSUE_JSON}" >> "${GITHUB_OUTPUT}"
echo "✅ Found issue #${{ github.event.issue.number }} from event to triage! 🎯"
- name: 'Find untriaged issues'
if: |-
${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
id: 'find_issues'
env:
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}'
@@ -61,26 +43,22 @@ jobs:
run: |-
set -euo pipefail
echo '🔍 Finding issues missing area labels...'
NO_AREA_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
--search 'is:open is:issue -label:area/core -label:area/agent -label:area/enterprise -label:area/non-interactive -label:area/security -label:area/platform -label:area/extensions -label:area/unknown' --limit 100 --json number,title,body)"
echo '🔍 Finding issues without labels...'
NO_LABEL_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
--search 'is:open is:issue no:label' --json number,title,body)"
echo '🔍 Finding issues missing kind labels...'
NO_KIND_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
--search 'is:open is:issue -label:kind/bug -label:kind/enhancement -label:kind/customer-issue -label:kind/question' --limit 100 --json number,title,body)"
echo '🏷️ Finding issues missing priority labels...'
NO_PRIORITY_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
--search 'is:open is:issue -label:priority/p0 -label:priority/p1 -label:priority/p2 -label:priority/p3 -label:priority/unknown' --limit 100 --json number,title,body)"
echo '🏷️ Finding issues that need triage...'
NEED_TRIAGE_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
--search "is:open is:issue label:\"status/need-triage\"" --limit 1000 --json number,title,body)"
echo '🔄 Merging and deduplicating issues...'
ISSUES="$(echo "${NO_AREA_ISSUES}" "${NO_KIND_ISSUES}" "${NO_PRIORITY_ISSUES}" | jq -c -s 'add | unique_by(.number)')"
ISSUES="$(echo "${NO_LABEL_ISSUES}" "${NEED_TRIAGE_ISSUES}" | jq -c -s 'add | unique_by(.number)')"
echo '📝 Setting output for GitHub Actions...'
echo "issues_to_triage=${ISSUES}" >> "${GITHUB_OUTPUT}"
ISSUE_COUNT="$(echo "${ISSUES}" | jq 'length')"
echo "✅ Found ${ISSUE_COUNT} unique issues to triage! 🎯"
echo "✅ Found ${ISSUE_COUNT} issues to triage! 🎯"
- name: 'Get Repository Labels'
id: 'get_labels'
@@ -99,13 +77,12 @@ jobs:
- name: 'Run Gemini Issue Analysis'
if: |-
(steps.get_issue_from_event.outputs.issues_to_triage != '' && steps.get_issue_from_event.outputs.issues_to_triage != '[]') ||
(steps.find_issues.outputs.issues_to_triage != '' && steps.find_issues.outputs.issues_to_triage != '[]')
${{ steps.find_issues.outputs.issues_to_triage != '[]' }}
uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # ratchet:google-github-actions/run-gemini-cli@v0
id: 'gemini_issue_analysis'
env:
GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs
ISSUES_TO_TRIAGE: '${{ steps.get_issue_from_event.outputs.issues_to_triage || steps.find_issues.outputs.issues_to_triage }}'
ISSUES_TO_TRIAGE: '${{ steps.find_issues.outputs.issues_to_triage }}'
REPOSITORY: '${{ github.repository }}'
AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}'
with:
@@ -139,32 +116,38 @@ jobs:
1. You are only able to use the echo command. Review the available labels in the environment variable: "${AVAILABLE_LABELS}".
2. Check environment variable for issues to triage: $ISSUES_TO_TRIAGE (JSON array of issues)
3. Review the issue title, body and any comments provided in the environment variables.
4. Identify the most relevant labels from the existing labels, specifically focusing on area/*, kind/* and priority/*.
5. Label Policy:
- If the issue already has a kind/ label, do not change it.
- If the issue already has a priority/ label, do not change it.
- If the issue already has an area/ label, do not change it.
- If any of these are missing, select exactly ONE appropriate label for the missing category.
4. Identify the most relevant labels from the existing labels, focusing on kind/*, area/*, sub-area/* and priority/*.
5. If the issue already has area/ label, dont try to change it. Similarly, if the issue already has a kind/ label don't change it. And if the issue already has a priority/ label do not change it for example:
If an issue has area/core and kind/bug you will only add a priority/ label.
Instead if an issue has no labels, you will could add one lable of each kind.
6. Identify other applicable labels based on the issue content, such as status/*, help wanted, good first issue, etc.
7. Give me a single short explanation about why you are selecting each label in the process.
8. Output a JSON array of objects, each containing the issue number
7. For area/* and kind/* limit yourself to only the single most applicable label in each case.
8. Give me a single short explanation about why you are selecting each label in the process.
9. Output a JSON array of objects, each containing the issue number
and the labels to add and remove, along with an explanation. For example:
```
[
{
"issue_number": 123,
"labels_to_add": ["area/core", "kind/bug", "priority/p2"],
"labels_to_add": ["kind/bug", "priority/p2"],
"labels_to_remove": ["status/need-triage"],
"explanation": "This issue is a UI bug that needs to be addressed with medium priority."
"explanation": "This issue is a bug that needs to be addressed with medium priority."
},
{
"issue_number": 456,
"labels_to_add": ["kind/enhancement"],
"labels_to_remove": [],
"explanation": "This issue is an enhancement request that could improve the user experience."
}
]
```
If an issue cannot be classified, do not include it in the output array.
9. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5
10. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5
- Anything more than 6 versions older than the most recent should add the status/need-retesting label
10. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below.
11. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation.
12. If you are uncertain about a category, use the area/unknown, kind/question, or priority/unknown labels as appropriate. If you are extremely uncertain, apply the status/manual-triage label.
11. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below.
- After identifying appropriate labels to an issue, add "status/need-triage" label to labels_to_remove in the output.
12. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation.
13. If you are uncertain and have not been able to apply one each of kind/, area/ and priority/ , apply the status/manual-triage label.
## Guidelines
@@ -174,46 +157,100 @@ jobs:
- Do not add comments or modify the issue content.
- Do not remove the following labels maintainer, help wanted or good first issue.
- Triage only the current issue.
- Identify only one area/ label.
- Identify only one area/ label
- Identify only one kind/ label (Do not apply kind/duplicate or kind/parent-issue)
- Identify only one priority/ label.
- Identify all applicable sub-area/* and priority/* labels based on the issue content. It's ok to have multiple of these.
- Once you categorize the issue if it needs information bump down the priority by 1 eg.. a p0 would become a p1 a p1 would become a p2. P2 and P3 can stay as is in this scenario.
Categorization Guidelines (Priority):
P0 - Urgent Blocking Issues:
- DO NOT APPLY THIS LABEL AUTOMATICALLY. Use status/manual-triage instead.
- Definition: Urgent, block a significant percentage of the user base, and prevent frequent use of the Gemini CLI.
- This includes core stability blockers (e.g., authentication failures, broken upgrades), critical crashes, and P0 security vulnerabilities.
- Impact: Blocks development or testing for the entire team; Major security vulnerability; Causes data loss or corruption with no workaround; Crashes the application or makes a core feature completely unusable for all or most users.
- Qualifier: Is the main function of the software broken?
P1 - High-Impact Issues:
- Definition: Affect a large number of users, blocking them from using parts of the Gemini CLI, or make the CLI frequently unusable even with workarounds available.
- Impact: A core feature is broken or behaving incorrectly for a large number of users or use cases; Severe performance degradation; No straightforward workaround exists.
- Qualifier: Is a key feature unusable or giving very wrong results?
P2 - Significant Issues:
- Definition: Affect some users significantly, such as preventing the use of certain features or authentication types.
- Can also be issues that many users complain about, causing annoyance or hindering daily use.
- Impact: Affects a non-critical feature or a smaller, specific subset of users; An inconvenient but functional workaround is available; Noticeable UI/UX problems that look unprofessional.
- Qualifier: Is it an annoying but non-blocking problem?
P3 - Low-Impact Issues:
- Definition: Typically usability issues that cause annoyance to a limited user base.
- Includes feature requests that could be addressed in the near future and may be suitable for community contributions.
- Impact: Minor cosmetic issues; An edge-case bug that is very difficult to reproduce and affects a tiny fraction of users.
- Qualifier: Is it a "nice-to-fix" issue?
Categorization Guidelines (Area):
area/agent: Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality
area/core: User Interface, OS Support, Core Functionality
area/enterprise: Telemetry, Policy, Quota / Licensing
area/extensions: Gemini CLI extensions capability
area/non-interactive: GitHub Actions, SDK, 3P Integrations, Shell Scripting, Command line automation
area/platform: Build infra, Release mgmt, Testing, Eval infra, Capacity, Quota mgmt
area/security: security related issues
Categorization Guidelines:
P0: Critical / Blocker
- A P0 bug is a catastrophic failure that demands immediate attention.
- To be a P0 it means almost all users are running into this issue and it is blocking users from being able to use the product.
- You would see this in the form of many comments from different developers on the bug.
- It represents a complete showstopper for a significant portion of users or for the development process itself.
Impact:
- Blocks development or testing for the entire team.
- Major security vulnerability that could compromise user data or system integrity.
- Causes data loss or corruption with no workaround.
- Crashes the application or makes a core feature completely unusable for all or most users in a production environment. Will it cause severe quality degration?
- Is it preventing contributors from contributing to the repository or is it a release blocker?
Qualifier: Is the main function of the software broken?
Example: The gemini auth login command fails with an unrecoverable error, preventing any user from authenticating and using the rest of the CLI.
P1: High
- A P1 bug is a serious issue that significantly degrades the user experience or impacts a core feature.
- While not a complete blocker, it's a major problem that needs a fast resolution. Feature requests are almost never P1.
- Once again this would be affecting many users.
- You would see this in the form of comments from different developers on the bug.
Impact:
- A core feature is broken or behaving incorrectly for a large number of users or large number of use cases.
- Review the bug details and comments to try figure out if this issue affects a large set of use cases or if it's a narrow set of use cases.
- Severe performance degradation making the application frustratingly slow.
- No straightforward workaround exists, or the workaround is difficult and non-obvious.
Qualifier: Is a key feature unusable or giving very wrong results?
Example: Gemini CLI enters a loop when making read-many-files tool call. I am unable to break out of the loop and gemini doesn't follow instructions subsequently.
P2: Medium
- A P2 bug is a moderately impactful issue. It's a noticeable problem but doesn't prevent the use of the software's main functionality.
Impact:
- Affects a non-critical feature or a smaller, specific subset of users.
- An inconvenient but functional workaround is available and easy to execute.
- Noticeable UI/UX problems that don't break functionality but look unprofessional (e.g., elements are misaligned or overlapping).
Qualifier: Is it an annoying but non-blocking problem?
Example: An error message is unclear or contains a typo, causing user confusion but not halting their workflow.
P3: Low
- A P3 bug is a minor, low-impact issue that is trivial or cosmetic. It has little to no effect on the overall functionality of the application.
Impact:
- Minor cosmetic issues like color inconsistencies, typos in documentation, or slight alignment problems on a non-critical page.
- An edge-case bug that is very difficult to reproduce and affects a tiny fraction of users.
Qualifier: Is it a "nice-to-fix" issue?
Example: Spelling mistakes etc.
Additional Context:
- If users are talking about issues where the model gets downgraded from pro to flash then i want you to categorize that as a performance issue.
- This product is designed to use different models eg.. using pro, downgrading to flash etc.
- When users report that they dont expect the model to change those would be categorized as feature requests.
- If users are talking about issues where the model gets downgraded from pro to flash then i want you to categorize that as a performance issue
- This product is designed to use different models eg.. using pro, downgrading to flash etc.
- When users report that they dont expect the model to change those would be categorized as feature requests.
Definition of Areas
area/ux:
- Issues concerning user-facing elements like command usability, interactive features, help docs, and perceived performance.
- I am seeing my screen flicker when using Gemini CLI
- I am seeing the output malformed
- Theme changes aren't taking effect
- My keyboard inputs arent' being recognzied
area/platform:
- Issues related to installation, packaging, OS compatibility (Windows, macOS, Linux), and the underlying CLI framework.
area/background: Issues related to long-running background tasks, daemons, and autonomous or proactive agent features.
area/models:
- i am not getting a response that is reasonable or expected. this can include things like
- I am calling a tool and the tool is not performing as expected.
- i am expecting a tool to be called and it is not getting called ,
- Including experience when using
- built-in tools (e.g., web search, code interpreter, read file, writefile, etc..),
- Function calling issues should be under this area
- i am getting responses from the model that are malformed.
- Issues concerning Gemini quality of response and inference,
- Issues talking about unnecessary token consumption.
- Issues talking about Model getting stuck in a loop be watchful as this could be the root cause for issues that otherwise seem like model performance issues.
- Memory compression
- unexpected responses,
- poor quality of generated code
area/tools:
- These are primarily issues related to Model Context Protocol
- These are issues that mention MCP support
- feature requests asking for support for new tools.
area/core:
- Issues with fundamental components like command parsing, configuration management, session state, and the main API client logic. Introducing multi-modality
area/contribution:
- Issues related to improving the developer contribution experience, such as CI/CD pipelines, build scripts, and test automation infrastructure.
area/authentication:
- Issues related to user identity, login flows, API key handling, credential storage, and access token management, unable to sign in selecting wrong authentication path etc..
area/security-privacy:
- Issues concerning vulnerability patching, dependency security, data sanitization, privacy controls, and preventing unauthorized data access.
area/extensibility:
- Issues related to the plugin system, extension APIs, or making the CLI's functionality available in other applications, github actions, ide support etc..
area/performance:
- Issues focused on model performance
- Issues with running out of capacity,
- 429 errors etc..
- could also pertain to latency,
- other general software performance like, memory usage, CPU consumption, and algorithmic efficiency.
- Switching models from one to the other unexpectedly.
- name: 'Apply Labels to Issues'
if: |-
@@ -263,6 +300,24 @@ jobs:
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}${explanation}`);
}
if (entry.labels_to_remove && entry.labels_to_remove.length > 0) {
for (const label of entry.labels_to_remove) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name: label
});
} catch (error) {
if (error.status !== 404) {
throw error;
}
}
}
core.info(`Successfully removed labels for #${issueNumber}: ${entry.labels_to_remove.join(', ')}`);
}
if (entry.explanation) {
await github.rest.issues.createComment({
owner: context.repo.owner,
@@ -39,7 +39,3 @@ jobs:
GITHUB_REPOSITORY: '${{ github.repository }}'
run: |-
./.github/scripts/pr-triage.sh
# If prs_needing_comment is empty, set it to [] explicitly for downstream steps
if [[ -z "$(grep 'prs_needing_comment' "${GITHUB_OUTPUT}" | cut -d'=' -f2-)" ]]; then
echo "prs_needing_comment=[]" >> "${GITHUB_OUTPUT}"
fi
@@ -1,46 +0,0 @@
name: '🏷️ Issue Opened Labeler'
on:
issues:
types:
- 'opened'
jobs:
label-issue:
runs-on: 'ubuntu-latest'
if: |-
${{ github.repository == 'google-gemini/gemini-cli' || github.repository == 'google-gemini/maintainers-gemini-cli' }}
steps:
- name: 'Generate GitHub App Token'
id: 'generate_token'
env:
APP_ID: '${{ secrets.APP_ID }}'
if: |-
${{ env.APP_ID != '' }}
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
with:
app-id: '${{ secrets.APP_ID }}'
private-key: '${{ secrets.PRIVATE_KEY }}'
- name: 'Add need-triage label'
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
with:
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
script: |-
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const hasLabel = issue.labels.some(l => l.name === 'status/need-triage');
if (!hasLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['status/need-triage']
});
} else {
core.info('Issue already has status/need-triage label. Skipping.');
}
@@ -1,57 +0,0 @@
name: 'Label Child Issues for Project Rollup'
on:
issues:
types: ['opened', 'edited', 'reopened']
schedule:
- cron: '0 * * * *' # Run every hour
workflow_dispatch:
permissions:
issues: 'write'
contents: 'read'
jobs:
# Event-based: Quick reaction to new/edited issues in THIS repo
labeler:
if: "github.event_name == 'issues'"
runs-on: 'ubuntu-latest'
steps:
- name: 'Checkout'
uses: 'actions/checkout@v4'
- name: 'Setup Node.js'
uses: 'actions/setup-node@v4'
with:
node-version: '20'
cache: 'npm'
- name: 'Install Dependencies'
run: 'npm ci'
- name: 'Run Multi-Repo Sync Script'
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
run: 'node .github/scripts/sync-maintainer-labels.cjs'
# Scheduled/Manual: Recursive sync across multiple repos
sync-maintainer-labels:
if: "github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'"
runs-on: 'ubuntu-latest'
steps:
- name: 'Checkout'
uses: 'actions/checkout@v4'
- name: 'Setup Node.js'
uses: 'actions/setup-node@v4'
with:
node-version: '20'
cache: 'npm'
- name: 'Install Dependencies'
run: 'npm ci'
- name: 'Run Multi-Repo Sync Script'
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
run: 'node .github/scripts/sync-maintainer-labels.cjs'
-119
View File
@@ -1,119 +0,0 @@
name: '🏷️ Enforce Restricted Label Permissions'
on:
issues:
types:
- 'labeled'
- 'unlabeled'
jobs:
enforce-label:
# Run this job only when restricted labels are changed
if: |-
${{ (github.event.label.name == 'help wanted' || github.event.label.name == 'status/need-triage' || github.event.label.name == '🔒 maintainer only') &&
(github.repository == 'google-gemini/gemini-cli' || github.repository == 'google-gemini/maintainers-gemini-cli') }}
runs-on: 'ubuntu-latest'
permissions:
issues: 'write'
steps:
- name: 'Generate GitHub App Token'
id: 'generate_token'
env:
APP_ID: '${{ secrets.APP_ID }}'
if: |-
${{ env.APP_ID != '' }}
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
with:
app-id: '${{ secrets.APP_ID }}'
private-key: '${{ secrets.PRIVATE_KEY }}'
- name: 'Check if user is in the maintainers team'
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
with:
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
script: |-
const org = context.repo.owner;
const username = context.payload.sender.login;
const team_slug = 'gemini-cli-maintainers';
const action = context.payload.action; // 'labeled' or 'unlabeled'
const labelName = context.payload.label.name;
// Skip if the change was made by a bot to avoid infinite loops
if (username === 'github-actions[bot]' || username === 'gemini-cli[bot]' || username.endsWith('[bot]')) {
core.info('Change made by a bot. Skipping.');
return;
}
try {
// Check repository permission level directly.
// This is more robust than team membership as it doesn't require Org-level read permissions
// and correctly handles Repo Admins/Writers who might not be in the specific team.
const { data: { permission } } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: org,
repo: context.repo.repo,
username,
});
if (permission === 'admin' || permission === 'write') {
core.info(`${username} has '${permission}' permission. Allowed.`);
return;
}
core.info(`${username} has '${permission}' permission (needs 'write' or 'admin'). Reverting '${action}' action for '${labelName}' label.`);
} catch (error) {
core.error(`Failed to check permissions for ${username}: ${error.message}`);
// Fall through to revert logic if we can't verify permissions (fail safe)
}
// If we are here, the user is NOT authorized.
if (true) { // wrapping block to preserve variable scope if needed
if (action === 'labeled') {
// 1. Remove the label if added by a non-maintainer
await github.rest.issues.removeLabel ({
owner: org,
repo: context.repo.repo,
issue_number: context.issue.number,
name: labelName
});
// 2. Post a polite comment
const comment = `
Hi @${username}, thank you for your interest in helping triage issues!
The \`${labelName}\` label is reserved for project maintainers to apply. This helps us ensure that an issue is ready and properly vetted for community contribution.
A maintainer will review this issue soon. Please see our [CONTRIBUTING.md](https://github.com/google-gemini/gemini-cli/blob/main/CONTRIBUTING.md) for more details on our labeling process.
`.trim().replace(/^[ ]+/gm, '');
await github.rest.issues.createComment ({
owner: org,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
} else if (action === 'unlabeled') {
// 1. Add the label back if removed by a non-maintainer
await github.rest.issues.addLabels ({
owner: org,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: [labelName]
});
// 2. Post a polite comment
const comment = `
Hi @${username}, it looks like the \`${labelName}\` label was removed.
This label is managed by project maintainers. We've added it back to ensure the issue remains visible to potential contributors until a maintainer decides otherwise.
Thank you for your understanding!
`.trim().replace(/^[ ]+/gm, '');
await github.rest.issues.createComment ({
owner: org,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
}
@@ -1,94 +0,0 @@
name: 'Label Workstream Rollup'
on:
issues:
types: ['opened', 'edited', 'reopened']
schedule:
- cron: '0 * * * *'
workflow_dispatch:
jobs:
labeler:
runs-on: 'ubuntu-latest'
permissions:
issues: 'write'
steps:
- name: 'Check for Parent Workstream and Apply Label'
uses: 'actions/github-script@v7'
with:
script: |
const labelToAdd = 'workstream-rollup';
// Allow-list of parent issue URLs
const allowedParentUrls = [
'https://github.com/google-gemini/gemini-cli/issues/15374',
'https://github.com/google-gemini/gemini-cli/issues/15456',
'https://github.com/google-gemini/gemini-cli/issues/15324'
];
async function getIssueParent(owner, repo, number) {
const query = `
query($owner:String!, $repo:String!, $number:Int!) {
repository(owner:$owner, name:$repo) {
issue(number:$number) {
parent {
url
}
}
}
}
`;
try {
const result = await github.graphql(query, { owner, repo, number });
return result.repository.issue.parent ? result.repository.issue.parent.url : null;
} catch (error) {
console.error(`Failed to fetch parent for #${number}:`, error);
return null;
}
}
// Determine which issues to process
let issuesToProcess = [];
if (context.eventName === 'issues') {
// Context payload for 'issues' event already has the issue object
issuesToProcess.push({
number: context.payload.issue.number,
owner: context.repo.owner,
repo: context.repo.repo
});
} else {
// For schedule/dispatch, fetch open issues (lightweight list)
console.log(`Running for event: ${context.eventName}. Fetching open issues...`);
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open'
});
issuesToProcess = openIssues.map(i => ({
number: i.number,
owner: context.repo.owner,
repo: context.repo.repo
}));
}
console.log(`Processing ${issuesToProcess.length} issue(s)...`);
for (const issue of issuesToProcess) {
const parentUrl = await getIssueParent(issue.owner, issue.repo, issue.number);
if (parentUrl && allowedParentUrls.includes(parentUrl)) {
console.log(`SUCCESS: Issue #${issue.number} is a direct child of ${parentUrl}. Adding label.`);
await github.rest.issues.addLabels({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
labels: [labelToAdd]
});
} else {
// logging only for single execution to avoid spam
if (context.eventName === 'issues') {
console.log(`Issue #${issue.number} parent is ${parentUrl || 'None'}. No action.`);
}
}
}
-2
View File
@@ -12,8 +12,6 @@ on:
jobs:
linkChecker:
if: |-
${{ github.repository == 'google-gemini/gemini-cli' }}
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
@@ -1,83 +0,0 @@
name: '🏷️ PR Contribution Guidelines Notifier'
on:
pull_request:
types:
- 'opened'
jobs:
notify-process-change:
runs-on: 'ubuntu-latest'
if: |-
github.repository == 'google-gemini/gemini-cli' || github.repository == 'google-gemini/maintainers-gemini-cli'
permissions:
pull-requests: 'write'
steps:
- name: 'Generate GitHub App Token'
id: 'generate_token'
env:
APP_ID: '${{ secrets.APP_ID }}'
if: |-
${{ env.APP_ID != '' }}
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
with:
app-id: '${{ secrets.APP_ID }}'
private-key: '${{ secrets.PRIVATE_KEY }}'
- name: 'Check membership and post comment'
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
with:
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
script: |-
const org = context.repo.owner;
const repo = context.repo.repo;
const username = context.payload.pull_request.user.login;
const pr_number = context.payload.pull_request.number;
// 1. Check if the PR author is a maintainer
const authorAssociation = context.payload.pull_request.author_association;
if (['OWNER', 'MEMBER', 'COLLABORATOR'].includes(authorAssociation)) {
core.info(`${username} is a maintainer (Association: ${authorAssociation}). No notification needed.`);
return;
}
// 2. Check if the PR is already associated with an issue
const query = `
query($owner:String!, $repo:String!, $number:Int!) {
repository(owner:$owner, name:$repo) {
pullRequest(number:$number) {
closingIssuesReferences(first: 1) {
totalCount
}
}
}
}
`;
const variables = { owner: org, repo: repo, number: pr_number };
const result = await github.graphql(query, variables);
const issueCount = result.repository.pullRequest.closingIssuesReferences.totalCount;
if (issueCount > 0) {
core.info(`PR #${pr_number} is already associated with an issue. No notification needed.`);
return;
}
// 3. Post the notification comment
core.info(`${username} is not a maintainer and PR #${pr_number} has no linked issue. Posting notification.`);
const comment = `
Hi @${username}, thank you so much for your contribution to Gemini CLI! We really appreciate the time and effort you've put into this.
We're making some updates to our contribution process to improve how we track and review changes. Please take a moment to review our recent discussion post: [Improving Our Contribution Process & Introducing New Guidelines](https://github.com/google-gemini/gemini-cli/discussions/16706).
Key Update: Starting **January 26, 2026**, the Gemini CLI project will require all pull requests to be associated with an existing issue. Any pull requests not linked to an issue by that date will be automatically closed.
Thank you for your understanding and for being a part of our community!
`.trim().replace(/^[ ]+/gm, '');
await github.rest.issues.createComment({
owner: org,
repo: repo,
issue_number: pr_number,
body: comment
});
-1
View File
@@ -31,7 +31,6 @@ on:
jobs:
release:
if: "github.repository == 'google-gemini/gemini-cli'"
environment: "${{ github.event.inputs.environment || 'prod' }}"
runs-on: 'ubuntu-latest'
permissions:
-5
View File
@@ -11,8 +11,6 @@
.gemini/*
!.gemini/config.yaml
!.gemini/commands/
!.gemini/skills/
!.gemini/settings.json
# Note: .gemini-clipboard/ is NOT in gitignore so Gemini can access pasted images
@@ -57,6 +55,3 @@ gha-creds-*.json
patch_output.log
.genkit
.gemini-clipboard/
.eslintcache
evals/logs/
-2
View File
@@ -17,7 +17,5 @@ eslint.config.js
**/generated
gha-creds-*.json
junit.xml
.gemini-linters/
Thumbs.db
.pytest_cache
**/SKILL.md
+4 -12
View File
@@ -42,13 +42,8 @@ This project follows
The process for contributing code is as follows:
1. **Find an issue** that you want to work on. If an issue is tagged as
`🔒Maintainers only`, this means it is reserved for project maintainers. We
will not accept pull requests related to these issues. In the near future,
we will explicitly mark issues looking for contributions using the
`help wanted` label. If you believe an issue is a good candidate for
community contribution, please leave a comment on the issue. A maintainer
will review it and apply the `help-wanted` label if appropriate. Only
maintainers should attempt to add the `help-wanted` label to an issue.
"🔒Maintainers only", this means it is reserved for project maintainers. We
will not accept pull requests related to these issues.
2. **Fork the repository** and create a new branch.
3. **Make your changes** in the `packages/` directory.
4. **Ensure all checks pass** by running `npm run preflight`.
@@ -99,11 +94,8 @@ any code is written.
- **For features:** The PR should be linked to the feature request or proposal
issue that has been approved by a maintainer.
If an issue for your change doesn't exist, we will automatically close your PR
along with a comment reminding you to associate the PR with an issue. The ideal
workflow starts with an issue that has been reviewed and approved by a
maintainer. Please **open the issue first** and wait for feedback before you
start coding.
If an issue for your change doesn't exist, please **open one first** and wait
for feedback before you start coding.
#### 2. Keep it small and focused
-20
View File
@@ -15,17 +15,6 @@ project. While you can run the individual steps (`build`, `test`, `typecheck`,
`lint`) separately, it is highly recommended to use `npm run preflight` to
ensure a comprehensive validation.
## Running Tests in Workspaces\*\*: To run a specific test file within a
workspace, use the command:
`npm test -w <workspace-name> -- <path/to/test-file.test.ts>`. **CRITICAL**: The
`<path/to/test-file.test.ts>` MUST be relative to the workspace directory root,
NOT the project root.
- _Example (Core package)_:
`npm test -w @google/gemini-cli-core -- src/routing/modelRouterService.test.ts`
- _Common workspaces_: `@google/gemini-cli`, `@google/gemini-cli-core`.
## Writing Tests
This project uses **Vitest** as its primary testing framework. When writing
@@ -402,15 +391,6 @@ When working in the `/docs` directory, follow the guidelines in this section:
Only write high-value comments if at all. Avoid talking to the user through
comments.
## Logging and Error Handling
- **Avoid Console Statements:** Do not use `console.log`, `console.error`, or
similar methods directly.
- **Non-User-Facing Logs:** For developer-facing debug messages, use
`debugLogger` (from `@google/gemini-cli-core`).
- **User-Facing Feedback:** To surface errors or warnings to the user, use
`coreEvents.emitFeedback` (from `@google/gemini-cli-core`).
## General requirements
- If there is something you do not understand or is ambiguous, seek confirmation
+4 -5
View File
@@ -18,8 +18,7 @@ Learn all about Gemini CLI in our [documentation](https://geminicli.com/docs/).
- **🎯 Free tier**: 60 requests/min and 1,000 requests/day with personal Google
account.
- **🧠 Powerful Gemini 3 models**: Access to improved reasoning and 1M token
context window.
- **🧠 Powerful Gemini 2.5 Pro**: Access to 1M token context window.
- **🔧 Built-in tools**: Google Search grounding, file operations, shell
commands, web fetching.
- **🔌 Extensible**: MCP (Model Context Protocol) support for custom
@@ -40,7 +39,7 @@ Learn all about Gemini CLI in our [documentation](https://geminicli.com/docs/).
```bash
# Using npx (no installation required)
npx @google/gemini-cli
npx https://github.com/google-gemini/gemini-cli
```
#### Install globally with npm
@@ -141,7 +140,7 @@ for details)
**Benefits:**
- **Free tier**: 60 requests/min and 1,000 requests/day
- **Gemini 3 models** with 1M token context window
- **Gemini 2.5 Pro** with 1M token context window
- **No API key management** - just sign in with your Google account
- **Automatic updates** to latest models
@@ -165,7 +164,7 @@ gemini
**Benefits:**
- **Free tier**: 1000 requests/day with Gemini 3 (mix of flash and pro)
- **Free tier**: 100 requests/day with Gemini 2.5 Pro
- **Model selection**: Choose specific Gemini models
- **Usage-based billing**: Upgrade for higher limits when needed
+3 -3
View File
@@ -13,11 +13,11 @@ input:
as handling the initial user input, presenting the final output, and
managing the overall user experience.
- **Key functions contained in the package:**
- [Input processing](/docs/cli/commands)
- [Input processing](/docs/cli/commands.md)
- History management
- Display rendering
- [Theme and UI customization](/docs/cli/themes)
- [CLI configuration settings](/docs/get-started/configuration)
- [Theme and UI customization](/docs/cli/themes.md)
- [CLI configuration settings](/docs/get-started/configuration.md)
2. **Core package (`packages/core`):**
- **Purpose:** This acts as the backend for the Gemini CLI. It receives
Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

+2 -83
View File
@@ -18,88 +18,6 @@ on GitHub.
| [Preview](preview.md) | Experimental features ready for early feedback. |
| [Stable](latest.md) | Stable, recommended for general use. |
## Announcements: v0.23.0 - 2026-01-07
- 🎉 **Experimental Agent Skills Support in Preview:** Gemini CLI now supports
[Agent Skills](https://agentskills.io/home) in our preview builds. This is an
early preview where were looking for feedback!
- Install Preview: `npm install -g @google/gemini-cli@preview`
- Enable in `/settings`
- Docs:
[https://geminicli.com/docs/cli/skills/](https://geminicli.com/docs/cli/skills/)
- **Gemini CLI wrapped:** Run `npx gemini-wrapped` to visualize your usage
stats, top models, languages, and more!
- **Windows clipboard image support:** Windows users can now paste images
directly from their clipboard into the CLI using `Alt`+`V`.
([pr](https://github.com/google-gemini/gemini-cli/pull/13997) by
[@sgeraldes](https://github.com/sgeraldes))
- **Terminal background color detection:** Automatically optimizes your
terminal's background color to select compatible themes and provide
accessibility warnings.
([pr](https://github.com/google-gemini/gemini-cli/pull/15132) by
[@jacob314](https://github.com/jacob314))
- **Session logout:** Use the new `/logout` command to instantly clear
credentials and reset your authentication state for seamless account
switching. ([pr](https://github.com/google-gemini/gemini-cli/pull/13383) by
[@CN-Scars](https://github.com/CN-Scars))
## Announcements: v0.22.0 - 2025-12-22
- 🎉**Free Tier + Gemini 3:** Free tier users now all have access to Gemini 3
Pro & Flash. Enable in `/settings` by toggling "Preview Features" to `true`.
- 🎉**Gemini CLI + Colab:** Gemini CLI is now pre-installed. Can be used
headlessly in notebook cells or interactively in the built-in terminal
([pic](https://imgur.com/a/G0Tn7vi))
- 🎉**Gemini CLI Extensions:**
- **Conductor:** Planning++, Gemini works with you to build out a detailed
plan, pull in extra details as needed, ultimately to give the LLM guardrails
with artifacts. Measure twice, implement once!
`gemini extensions install https://github.com/gemini-cli-extensions/conductor`
Blog:
[https://developers.googleblog.com/conductor-introducing-context-driven-development-for-gemini-cli/](https://developers.googleblog.com/conductor-introducing-context-driven-development-for-gemini-cli/)
- **Endor Labs:** Perform code analysis, vulnerability scanning, and
dependency checks using natural language.
`gemini extensions install https://github.com/endorlabs/gemini-extension`
## Announcements: v0.21.0 - 2025-12-15
- **⚡️⚡️⚡️ Gemini 3 Flash + Gemini CLI:** Better, faster and cheaper than 2.5
Pro - and in some scenarios better than 3 Pro! For paid tiers + free tier
users who were on the wait list enable **Preview Features** in `/settings.`
- For more information:
[Gemini 3 Flash is now available in Gemini CLI](https://developers.googleblog.com/gemini-3-flash-is-now-available-in-gemini-cli/).
- 🎉 Gemini CLI Extensions:
- Rill: Utilize natural language to analyze Rill data, enabling the
exploration of metrics and trends without the need for manual queries.
`gemini extensions install https://github.com/rilldata/rill-gemini-extension`
- Browserbase: Interact with web pages, take screenshots, extract information,
and perform automated actions with atomic precision.
`gemini extensions install https://github.com/browserbase/mcp-server-browserbase`
- Quota Visibility: The `/stats` command now displays quota information for all
available models, including those not used in the current session. (@sehoon38)
- Fuzzy Setting Search: Users can now quickly find settings using fuzzy search
within the settings dialog. (@sehoon38)
- MCP Resource Support: Users can now discover, view, and search through
resources using the @ command. (@MrLesk)
- Auto-execute Simple Slash Commands: Simple slash commands are now executed
immediately on enter. (@jackwotherspoon)
## Announcements: v0.20.0 - 2025-12-01
- **Multi-file Drag & Drop:** Users can now drag and drop multiple files into
the terminal, and the CLI will automatically prefix each valid path with `@`.
([pr](https://github.com/google-gemini/gemini-cli/pull/14832) by
[@jackwotherspoon](https://github.com/jackwotherspoon))
- **Persistent "Always Allow" Policies:** Users can now save "Always Allow"
decisions for tool executions, with granular control over specific shell
commands and multi-cloud platform tools.
([pr](https://github.com/google-gemini/gemini-cli/pull/14737) by
[@allenhutchison](https://github.com/allenhutchison))
## Announcements: v0.19.0 - 2025-11-24
- 🎉 **New extensions:**
@@ -136,7 +54,8 @@ on GitHub.
- **Experimental permission improvements:** We are now experimenting with a new
policy engine in Gemini CLI. This allows users and administrators to create
fine-grained policy for tool calls. Currently behind a flag. See
[policy engine documentation](../core/policy-engine.md) for more information.
[https://geminicli.com/docs/core/policy-engine/](../core/policy-engine.md) for
more information.
- Blog:
[https://allen.hutchison.org/2025/11/26/the-guardrails-of-autonomy/](https://allen.hutchison.org/2025/11/26/the-guardrails-of-autonomy/)
- **Gemini 3 support for paid:** Gemini 3 support has been rolled out to all API
+139 -150
View File
@@ -1,6 +1,6 @@
# Latest stable release: v0.23.0
# Latest stable release: v0.19.0 - v0.19.4
Released: January 6, 2026
Released: December 1, 2025
For most users, our latest stable release is the recommended release. Install
the latest stable version with:
@@ -11,155 +11,144 @@ npm install -g @google/gemini-cli
## Highlights
- **Gemini CLI wrapped:** Run `npx gemini-wrapped` to visualize your usage
stats, top models, languages, and more!
- **Windows clipboard image support:** Windows users can now paste images
directly from their clipboard into the CLI using `Alt`+`V`.
([pr](https://github.com/google-gemini/gemini-cli/pull/13997) by
[@sgeraldes](https://github.com/sgeraldes))
- **Terminal background color detection:** Automatically optimizes your
terminal's background color to select compatible themes and provide
accessibility warnings.
([pr](https://github.com/google-gemini/gemini-cli/pull/15132) by
[@jacob314](https://github.com/jacob314))
- **Session logout:** Use the new `/logout` command to instantly clear
credentials and reset your authentication state for seamless account
switching. ([pr](https://github.com/google-gemini/gemini-cli/pull/13383) by
[@CN-Scars](https://github.com/CN-Scars))
- **Zed integration:** Users can now leverage Gemini 3 within the Zed
integration after enabling "Preview Features" in their CLIs `/settings`.
- **Interactive shell:**
- **Click-to-Focus:** Go to `/settings` and enable **Use Alternate Buffer** to
click within the embedded shell output to focus it for input.
- **Loading phrase:** Clearly indicates when the interactive shell is awaiting
user input. ([vid](https://imgur.com/a/kjK8bUK),
[pr](https://github.com/google-gemini/gemini-cli/pull/12535) by
[@jackwotherspoon](https://github.com/jackwotherspoon))
## What's changed
## What's Changed
- Code assist service metrics. by @gundermanc in
https://github.com/google-gemini/gemini-cli/pull/15024
- chore/release: bump version to 0.21.0-nightly.20251216.bb0c0d8ee by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15121
- Docs by @Roaimkhan in https://github.com/google-gemini/gemini-cli/pull/15103
- Use official ACP SDK and support HTTP/SSE based MCP servers by @SteffenDE in
https://github.com/google-gemini/gemini-cli/pull/13856
- Remove foreground for themes other than shades of purple and holiday. by
@jacob314 in https://github.com/google-gemini/gemini-cli/pull/14606
- chore: remove repo specific tips by @jackwotherspoon in
https://github.com/google-gemini/gemini-cli/pull/15164
- chore: remove user query from footer in debug mode by @jackwotherspoon in
https://github.com/google-gemini/gemini-cli/pull/15169
- Disallow unnecessary awaits. by @gundermanc in
https://github.com/google-gemini/gemini-cli/pull/15172
- Add one to the padding in settings dialog to avoid flicker. by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/15173
- feat(core): introduce remote agent infrastructure and rename local executor by
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/15110
- feat(cli): Add `/auth logout` command to clear credentials and auth state by
@CN-Scars in https://github.com/google-gemini/gemini-cli/pull/13383
- (fix) Automated pr labeller by @DaanVersavel in
https://github.com/google-gemini/gemini-cli/pull/14885
- feat: launch Gemini 3 Flash in Gemini CLI ⚡️⚡️⚡️ by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/15196
- Refactor: Migrate console.error in ripGrep.ts to debugLogger by @Adib234 in
https://github.com/google-gemini/gemini-cli/pull/15201
- chore: update a2a-js to 0.3.7 by @adamfweidman in
https://github.com/google-gemini/gemini-cli/pull/15197
- chore(core): remove redundant isModelAvailabilityServiceEnabled toggle and
clean up dead code by @adamfweidman in
https://github.com/google-gemini/gemini-cli/pull/15207
- feat(core): Late resolve `GenerateContentConfig`s and reduce mutation. by
@joshualitt in https://github.com/google-gemini/gemini-cli/pull/14920
- Respect previewFeatures value from the remote flag if undefined by @sehoon38
in https://github.com/google-gemini/gemini-cli/pull/15214
- feat(ui): add Windows clipboard image support and Alt+V paste workaround by
@jacob314 in https://github.com/google-gemini/gemini-cli/pull/15218
- chore(core): remove legacy fallback flags and migrate loop detection by
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/15213
- fix(ui): Prevent eager slash command completion hiding sibling commands by
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15224
- Docs: Update Changelog for Dec 17, 2025 by @jkcinouye in
https://github.com/google-gemini/gemini-cli/pull/15204
- Code Assist backend telemetry for user accept/reject of suggestions by
@gundermanc in https://github.com/google-gemini/gemini-cli/pull/15206
- fix(cli): correct initial history length handling for chat commands by
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15223
- chore/release: bump version to 0.21.0-nightly.20251218.739c02bd6 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15231
- Change detailed model stats to use a new shared Table class to resolve
robustness issues. by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/15208
- feat: add agent toml parser by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15112
- Add core tool that adds all context from the core package. by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/15238
- (docs): Add reference section to hooks documentation by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15159
- feat(hooks): add support for friendly names and descriptions by @abhipatel12
in https://github.com/google-gemini/gemini-cli/pull/15174
- feat: Detect background color by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/15132
- add 3.0 to allowed sensitive keywords by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/15276
- feat: Pass additional environment variables to shell execution by @galz10 in
https://github.com/google-gemini/gemini-cli/pull/15160
- Remove unused code by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/15290
- Handle all 429 as retryableQuotaError by @sehoon38 in
https://github.com/google-gemini/gemini-cli/pull/15288
- Remove unnecessary dependencies by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/15291
- fix: prevent infinite loop in prompt completion on error by @galz10 in
https://github.com/google-gemini/gemini-cli/pull/14548
- fix(ui): show command suggestions even on perfect match and sort them by
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15287
- feat(hooks): reduce log verbosity and improve error reporting in UI by
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/15297
- feat: simplify tool confirmation labels for better UX by @NTaylorMullen in
https://github.com/google-gemini/gemini-cli/pull/15296
- chore/release: bump version to 0.21.0-nightly.20251219.70696e364 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15301
- feat(core): Implement JIT context memory loading and UI sync by @SandyTao520
in https://github.com/google-gemini/gemini-cli/pull/14469
- feat(ui): Put "Allow for all future sessions" behind a setting off by default.
by @jacob314 in https://github.com/google-gemini/gemini-cli/pull/15322
- fix(cli):change the placeholder of input during the shell mode by
@JayadityaGit in https://github.com/google-gemini/gemini-cli/pull/15135
- Validate OAuth resource parameter matches MCP server URL by @galz10 in
https://github.com/google-gemini/gemini-cli/pull/15289
- docs(cli): add System Prompt Override (GEMINI_SYSTEM_MD) by @ashmod in
https://github.com/google-gemini/gemini-cli/pull/9515
- more robust command parsing logs by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/15339
- Introspection agent demo by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/15232
- fix(core): sanitize hook command expansion and prevent injection by
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15343
- fix(folder trust): add validation for trusted folder level by @adamfweidman in
https://github.com/google-gemini/gemini-cli/pull/12215
- fix(cli): fix right border overflow in trust dialogs by @galz10 in
https://github.com/google-gemini/gemini-cli/pull/15350
- fix(policy): fix bug where accepting-edits continued after it was turned off
by @jacob314 in https://github.com/google-gemini/gemini-cli/pull/15351
- fix: prevent infinite relaunch loop when --resume fails (#14941) by @Ying-xi
in https://github.com/google-gemini/gemini-cli/pull/14951
- chore/release: bump version to 0.21.0-nightly.20251220.41a1a3eed by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15352
- feat(telemetry): add clearcut logging for hooks by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15405
- fix(core): Add `.geminiignore` support to SearchText tool by @xyrolle in
https://github.com/google-gemini/gemini-cli/pull/13763
- fix(patch): cherry-pick 0843d9a to release/v0.23.0-preview.0-pr-15443 to patch
version v0.23.0-preview.0 and create version 0.23.0-preview.1 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15445
- fix(patch): cherry-pick 9cdb267 to release/v0.23.0-preview.1-pr-15494 to patch
version v0.23.0-preview.1 and create version 0.23.0-preview.2 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15592
- fix(patch): cherry-pick 37be162 to release/v0.23.0-preview.2-pr-15601 to patch
version v0.23.0-preview.2 and create version 0.23.0-preview.3 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15603
- fix(patch): cherry-pick 07e597d to release/v0.23.0-preview.3-pr-15684
- Use lenient MCP output schema validator by @cornmander in
https://github.com/google-gemini/gemini-cli/pull/13521
- Update persistence state to track counts of messages instead of times banner
has been displayed by @Adib234 in
https://github.com/google-gemini/gemini-cli/pull/13428
- update docs for http proxy by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/13538
- move stdio by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/13528
- chore(release): bump version to 0.19.0-nightly.20251120.8e531dc02 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13540
- Skip pre-commit hooks for shadow repo (#13331) by @vishvananda in
https://github.com/google-gemini/gemini-cli/pull/13488
- fix(ui): Correct mouse click cursor positioning for wide characters by
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/13537
- fix(core): correct bash @P prompt transformation detection by @pyrytakala in
https://github.com/google-gemini/gemini-cli/pull/13544
- Optimize and improve test coverage for cli/src/config by @megha1188 in
https://github.com/google-gemini/gemini-cli/pull/13485
- Improve code coverage for cli/src/ui/privacy package by @megha1188 in
https://github.com/google-gemini/gemini-cli/pull/13493
- docs: fix typos in source code and documentation by @fancive in
https://github.com/google-gemini/gemini-cli/pull/13577
- Improved code coverage for cli/src/zed-integration by @megha1188 in
https://github.com/google-gemini/gemini-cli/pull/13570
- feat(ui): build interactive session browser component by @bl-ue in
https://github.com/google-gemini/gemini-cli/pull/13351
- Fix multiple bugs with auth flow including using the implemented but unused
restart support. by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/13565
- feat(core): add modelAvailabilityService for managing and tracking model
health by @adamfweidman in
https://github.com/google-gemini/gemini-cli/pull/13426
- docs: fix grammar typo "a MCP" to "an MCP" by @noahacgn in
https://github.com/google-gemini/gemini-cli/pull/13595
- feat: custom loading phrase when interactive shell requires input by
@jackwotherspoon in https://github.com/google-gemini/gemini-cli/pull/12535
- docs: Update uninstall command to reflect multiple extension support by
@JayadityaGit in https://github.com/google-gemini/gemini-cli/pull/13582
- bug(core): Ensure we use thinking budget on fallback to 2.5 by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/13596
- Remove useModelRouter experimental flag by @Adib234 in
https://github.com/google-gemini/gemini-cli/pull/13593
- feat(docs): Ensure multiline JS objects are rendered properly. by @joshualitt
in https://github.com/google-gemini/gemini-cli/pull/13535
- Fix exp id logging by @owenofbrien in
https://github.com/google-gemini/gemini-cli/pull/13430
- Moved client id logging into createBasicLogEvent by @owenofbrien in
https://github.com/google-gemini/gemini-cli/pull/13607
- Restore bracketed paste mode after external editor exit by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/13606
- feat(core): Add support for custom aliases for model configs. by @joshualitt
in https://github.com/google-gemini/gemini-cli/pull/13546
- feat(core): Add `BaseLlmClient.generateContent`. by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/13591
- Turn off alternate buffer mode by default. by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/13623
- fix(cli): Prevent stdout/stderr patching for extension commands by @chrstnb in
https://github.com/google-gemini/gemini-cli/pull/13600
- Improve test coverage for cli/src/ui/components by @megha1188 in
https://github.com/google-gemini/gemini-cli/pull/13598
- Update ink version to 6.4.6 by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/13631
- chore/release: bump version to 0.19.0-nightly.20251122.42c2e1b21 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13637
- chore/release: bump version to 0.19.0-nightly.20251123.dadd606c0 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13675
- chore/release: bump version to 0.19.0-nightly.20251124.e177314a4 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13713
- fix(core): Fix context window overflow warning for PDF files by @kkitase in
https://github.com/google-gemini/gemini-cli/pull/13548
- feat :rephrasing the extension logging messages to run the explore command
when there are no extensions installed by @JayadityaGit in
https://github.com/google-gemini/gemini-cli/pull/13740
- Improve code coverage for cli package by @megha1188 in
https://github.com/google-gemini/gemini-cli/pull/13724
- Add session subtask in /stats command by @Adib234 in
https://github.com/google-gemini/gemini-cli/pull/13750
- feat(core): Migrate chatCompressionService to model configs. by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/12863
- feat(hooks): Hook Telemetry Infrastructure by @Edilmo in
https://github.com/google-gemini/gemini-cli/pull/9082
- fix: (some minor improvements to configs and getPackageJson return behaviour)
by @grMLEqomlkkU5Eeinz4brIrOVCUCkJuN in
https://github.com/google-gemini/gemini-cli/pull/12510
- feat(hooks): Hook Event Handling by @Edilmo in
https://github.com/google-gemini/gemini-cli/pull/9097
- feat(hooks): Hook Agent Lifecycle Integration by @Edilmo in
https://github.com/google-gemini/gemini-cli/pull/9105
- feat(core): Land bool for alternate system prompt. by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/13764
- bug(core): Add default chat compression config. by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/13766
- feat(model-availability): introduce ModelPolicy and PolicyCatalog by
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/13751
- feat(hooks): Hook System Orchestration by @Edilmo in
https://github.com/google-gemini/gemini-cli/pull/9102
- feat(config): add isModelAvailabilityServiceEnabled setting by @adamfweidman
in https://github.com/google-gemini/gemini-cli/pull/13777
- chore/release: bump version to 0.19.0-nightly.20251125.f6d97d448 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13782
- chore: remove console.error by @adamfweidman in
https://github.com/google-gemini/gemini-cli/pull/13779
- fix: Add $schema property to settings.schema.json by @sacrosanctic in
https://github.com/google-gemini/gemini-cli/pull/12763
- fix(cli): allow non-GitHub SCP-styled URLs for extension installation by @m0ps
in https://github.com/google-gemini/gemini-cli/pull/13800
- fix(resume): allow passing a prompt via stdin while resuming using --resume by
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13520
- feat(sessions): add /resume slash command to open the session browser by
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13621
- docs(sessions): add documentation for chat recording and session management by
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13667
- Fix TypeError: "URL.parse is not a function" for Node.js < v22 by @macarronesc
in https://github.com/google-gemini/gemini-cli/pull/13698
- fallback to flash for TerminalQuota errors by @sehoon38 in
https://github.com/google-gemini/gemini-cli/pull/13791
- Update Code Wiki README badge by @PatoBeltran in
https://github.com/google-gemini/gemini-cli/pull/13768
- Add Databricks auth support and custom header option to gemini cli by
@AarushiShah in https://github.com/google-gemini/gemini-cli/pull/11893
- Update dependency for modelcontextprotocol/sdk to 1.23.0 by @bbiggs in
https://github.com/google-gemini/gemini-cli/pull/13827
- fix(patch): cherry-pick 576fda1 to release/v0.19.0-preview.0-pr-14099
[CONFLICTS] by @gemini-cli-robot in
https://github.com/google-gemini/gemini-cli/pull/15734
- fix(patch): cherry-pick c31f053 to release/v0.23.0-preview.4-pr-16004 to patch
version v0.23.0-preview.4 and create version 0.23.0-preview.5 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/16027
- fix(patch): cherry-pick 788bb04 to release/v0.23.0-preview.5-pr-15817
[CONFLICTS] by @gemini-cli-robot in
https://github.com/google-gemini/gemini-cli/pull/16038
https://github.com/google-gemini/gemini-cli/pull/14402
**Full changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.22.5...v0.23.0
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.18.4...v0.19.0
+127 -210
View File
@@ -1,6 +1,6 @@
# Preview release: Release v0.24.0-preview.0
# Preview release: Release v0.19.0-preview.0
Released: January 6, 2026
Released: November 25, 2025
Our preview release includes the latest, new, and experimental features. This
release may not be as stable as our [latest weekly release](latest.md).
@@ -13,212 +13,129 @@ npm install -g @google/gemini-cli@preview
## What's changed
- chore(core): refactor model resolution and cleanup fallback logic by
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/15228
- Add Folder Trust Support To Hooks by @sehoon38 in
https://github.com/google-gemini/gemini-cli/pull/15325
- Record timestamp with code assist metrics. by @gundermanc in
https://github.com/google-gemini/gemini-cli/pull/15439
- feat(policy): implement dynamic mode-aware policy evaluation by @abhipatel12
in https://github.com/google-gemini/gemini-cli/pull/15307
- fix(core): use debugLogger.debug for startup profiler logs by @NTaylorMullen
in https://github.com/google-gemini/gemini-cli/pull/15443
- feat(ui): Add security warning and improve layout for Hooks list by
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15440
- fix #15369, prevent crash on unhandled EIO error in readStdin cleanup by
@ElecTwix in https://github.com/google-gemini/gemini-cli/pull/15410
- chore: improve error messages for --resume by @jackwotherspoon in
https://github.com/google-gemini/gemini-cli/pull/15360
- chore: remove clipboard file by @jackwotherspoon in
https://github.com/google-gemini/gemini-cli/pull/15447
- Implemented unified secrets sanitization and env. redaction options by
@gundermanc in https://github.com/google-gemini/gemini-cli/pull/15348
- feat: automatic `/model` persistence across Gemini CLI sessions by @niyasrad
in https://github.com/google-gemini/gemini-cli/pull/13199
- refactor(core): remove deprecated permission aliases from BeforeToolHookOutput
by @StoyanD in https://github.com/google-gemini/gemini-cli/pull/14855
- fix: add missing `type` field to MCPServerConfig by @jackwotherspoon in
https://github.com/google-gemini/gemini-cli/pull/15465
- Make schema validation errors non-fatal by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/15487
- chore: limit MCP resources display to 10 by default by @jackwotherspoon in
https://github.com/google-gemini/gemini-cli/pull/15489
- Add experimental in-CLI extension install and uninstall subcommands by
@chrstnb in https://github.com/google-gemini/gemini-cli/pull/15178
- feat: Add A2A Client Manager and tests by @adamfweidman in
https://github.com/google-gemini/gemini-cli/pull/15485
- feat: terse transformations of image paths in text buffer by @psinha40898 in
https://github.com/google-gemini/gemini-cli/pull/4924
- Security: Project-level hook warnings by @sehoon38 in
https://github.com/google-gemini/gemini-cli/pull/15470
- Added modifyOtherKeys protocol support for tmux by @ved015 in
https://github.com/google-gemini/gemini-cli/pull/15524
- chore(core): fix comment typo by @Mapleeeeeeeeeee in
https://github.com/google-gemini/gemini-cli/pull/15558
- feat: Show snowfall animation for holiday theme by @sehoon38 in
https://github.com/google-gemini/gemini-cli/pull/15494
- do not persist the fallback model by @sehoon38 in
https://github.com/google-gemini/gemini-cli/pull/15483
- Resolve unhandled promise rejection in ide-client.ts by @Adib234 in
https://github.com/google-gemini/gemini-cli/pull/15587
- fix(core): handle checkIsRepo failure in GitService.initialize by
@Mapleeeeeeeeeee in https://github.com/google-gemini/gemini-cli/pull/15574
- fix(cli): add enableShellOutputEfficiency to settings schema by
@Mapleeeeeeeeeee in https://github.com/google-gemini/gemini-cli/pull/15560
- Manual nightly version bump to 0.24.0-nightly.20251226.546baf993 by @galz10 in
https://github.com/google-gemini/gemini-cli/pull/15594
- refactor(core): extract static concerns from CoreToolScheduler by @abhipatel12
in https://github.com/google-gemini/gemini-cli/pull/15589
- fix(core): enable granular shell command allowlisting in policy engine by
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/15601
- chore/release: bump version to 0.24.0-nightly.20251227.37be16243 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15612
- refactor: deprecate legacy confirmation settings and enforce Policy Engine by
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/15626
- Migrate console to coreEvents.emitFeedback or debugLogger by @Adib234 in
https://github.com/google-gemini/gemini-cli/pull/15219
- Exponential back-off retries for retryable error without a specified … by
@sehoon38 in https://github.com/google-gemini/gemini-cli/pull/15684
- feat(agents): add support for remote agents and multi-agent TOML files by
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/15437
- Update wittyPhrases.ts by @segyges in
https://github.com/google-gemini/gemini-cli/pull/15697
- refactor(auth): Refactor non-interactive mode auth validation & refresh by
@skeshive in https://github.com/google-gemini/gemini-cli/pull/15679
- Revert "Update wittyPhrases.ts (#15697)" by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15719
- fix(hooks): deduplicate agent hooks and add cross-platform integration tests
by @abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/15701
- Implement support for tool input modification by @gundermanc in
https://github.com/google-gemini/gemini-cli/pull/15492
- Add instructions to the extensions update info notification by @chrstnb in
https://github.com/google-gemini/gemini-cli/pull/14907
- Add extension settings info to /extensions list by @chrstnb in
https://github.com/google-gemini/gemini-cli/pull/14905
- Agent Skills: Implement Core Skill Infrastructure & Tiered Discovery by
@NTaylorMullen in https://github.com/google-gemini/gemini-cli/pull/15698
- chore: remove cot style comments by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15735
- feat(agents): Add remote agents to agent registry by @sehoon38 in
https://github.com/google-gemini/gemini-cli/pull/15711
- feat(hooks): implement STOP_EXECUTION and enhance hook decision handling by
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15685
- Fix build issues caused by year-specific linter rule by @gundermanc in
https://github.com/google-gemini/gemini-cli/pull/15780
- fix(core): handle unhandled promise rejection in mcp-client-manager by
@kamja44 in https://github.com/google-gemini/gemini-cli/pull/14701
- log fallback mode by @sehoon38 in
https://github.com/google-gemini/gemini-cli/pull/15817
- Agent Skills: Implement Autonomous Activation Tool & Context Injection by
@NTaylorMullen in https://github.com/google-gemini/gemini-cli/pull/15725
- fix(core): improve shell command with redirection detection by @galz10 in
https://github.com/google-gemini/gemini-cli/pull/15683
- Add security docs by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15739
- feat: add folder suggestions to `/dir add` command by @jackwotherspoon in
https://github.com/google-gemini/gemini-cli/pull/15724
- Agent Skills: Implement Agent Integration and System Prompt Awareness by
@NTaylorMullen in https://github.com/google-gemini/gemini-cli/pull/15728
- chore: cleanup old smart edit settings by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15832
- Agent Skills: Status Bar Integration for Skill Counts by @NTaylorMullen in
https://github.com/google-gemini/gemini-cli/pull/15741
- fix(core): mock powershell output in shell-utils test by @galz10 in
https://github.com/google-gemini/gemini-cli/pull/15831
- Agent Skills: Unify Representation & Centralize Loading by @NTaylorMullen in
https://github.com/google-gemini/gemini-cli/pull/15833
- Unify shell security policy and remove legacy logic by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15770
- feat(core): restore MessageBus optionality for soft migration (Phase 1) by
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/15774
- feat(core): Standardize Tool and Agent Invocation constructors (Phase 2) by
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/15775
- feat(core,cli): enforce mandatory MessageBus injection (Phase 3 Hard
Migration) by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15776
- Agent Skills: Extension Support & Security Disclosure by @NTaylorMullen in
https://github.com/google-gemini/gemini-cli/pull/15834
- feat(hooks): implement granular stop and block behavior for agent hooks by
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15824
- Agent Skills: Add gemini skills CLI management command by @NTaylorMullen in
https://github.com/google-gemini/gemini-cli/pull/15837
- refactor: consolidate EditTool and SmartEditTool by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15857
- fix(cli): mock fs.readdir in consent tests for Windows compatibility by
@NTaylorMullen in https://github.com/google-gemini/gemini-cli/pull/15904
- refactor(core): Extract and integrate ToolExecutor by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15900
- Fix terminal hang when user exits browser without logging in by @gundermanc in
https://github.com/google-gemini/gemini-cli/pull/15748
- fix: avoid SDK warning by not accessing .text getter in logging by @ved015 in
https://github.com/google-gemini/gemini-cli/pull/15706
- Make default settings apply by @devr0306 in
https://github.com/google-gemini/gemini-cli/pull/15354
- chore: rename smart-edit to edit by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15923
- Opt-in to persist model from /model by @sehoon38 in
https://github.com/google-gemini/gemini-cli/pull/15820
- fix: prevent /copy crash on Windows by skipping /dev/tty by @ManojINaik in
https://github.com/google-gemini/gemini-cli/pull/15657
- Support context injection via SessionStart hook. by @gundermanc in
https://github.com/google-gemini/gemini-cli/pull/15746
- Fix order of preflight by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/15941
- Fix failing unit tests by @gundermanc in
https://github.com/google-gemini/gemini-cli/pull/15940
- fix(cli): resolve paste issue on Windows terminals. by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/15932
- Agent Skills: Implement /skills reload by @NTaylorMullen in
https://github.com/google-gemini/gemini-cli/pull/15865
- Add setting to support OSC 52 paste by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/15336
- remove manual string when displaying manual model in the footer by @sehoon38
in https://github.com/google-gemini/gemini-cli/pull/15967
- fix(core): use correct interactive check for system prompt by @ppergame in
https://github.com/google-gemini/gemini-cli/pull/15020
- Inform user of missing settings on extensions update by @chrstnb in
https://github.com/google-gemini/gemini-cli/pull/15944
- feat(policy): allow 'modes' in user and admin policies by @NTaylorMullen in
https://github.com/google-gemini/gemini-cli/pull/15977
- fix: default folder trust to untrusted for enhanced security by @galz10 in
https://github.com/google-gemini/gemini-cli/pull/15943
- Add description for each settings item in /settings by @sehoon38 in
https://github.com/google-gemini/gemini-cli/pull/15936
- Use GetOperation to poll for OnboardUser completion by @ishaanxgupta in
https://github.com/google-gemini/gemini-cli/pull/15827
- Agent Skills: Add skill directory to WorkspaceContext upon activation by
@NTaylorMullen in https://github.com/google-gemini/gemini-cli/pull/15870
- Fix settings command fallback by @chrstnb in
https://github.com/google-gemini/gemini-cli/pull/15926
- fix: writeTodo construction by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/16014
- properly disable keyboard modes on exit by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/16006
- Add workflow to label child issues for rollup by @bdmorgan in
https://github.com/google-gemini/gemini-cli/pull/16002
- feat(ui): add visual indicators for hook execution by @abhipatel12 in
https://github.com/google-gemini/gemini-cli/pull/15408
- fix: image token estimation by @jackwotherspoon in
https://github.com/google-gemini/gemini-cli/pull/16004
- feat(hooks): Add a hooks.enabled setting. by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/15933
- feat(admin): Introduce remote admin settings & implement
secureModeEnabled/mcpEnabled by @skeshive in
https://github.com/google-gemini/gemini-cli/pull/15935
- Remove trailing whitespace in yaml. by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/16036
- feat(agents): add support for remote agents by @adamfweidman in
https://github.com/google-gemini/gemini-cli/pull/16013
- fix: limit scheduled issue triage queries to prevent argument list too long
error by @jerop in https://github.com/google-gemini/gemini-cli/pull/16021
- ci(github-actions): triage all new issues automatically by @jerop in
https://github.com/google-gemini/gemini-cli/pull/16018
- Fix test. by @gundermanc in
https://github.com/google-gemini/gemini-cli/pull/16011
- fix: hide broken skills object from settings dialog by @korade-krushna in
https://github.com/google-gemini/gemini-cli/pull/15766
- Agent Skills: Initial Documentation & Tutorial by @NTaylorMullen in
https://github.com/google-gemini/gemini-cli/pull/15869
- Use lenient MCP output schema validator by @cornmander in
https://github.com/google-gemini/gemini-cli/pull/13521
- Update persistence state to track counts of messages instead of times banner
has been displayed by @Adib234 in
https://github.com/google-gemini/gemini-cli/pull/13428
- update docs for http proxy by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/13538
- move stdio by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/13528
- chore(release): bump version to 0.19.0-nightly.20251120.8e531dc02 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13540
- Skip pre-commit hooks for shadow repo (#13331) by @vishvananda in
https://github.com/google-gemini/gemini-cli/pull/13488
- fix(ui): Correct mouse click cursor positioning for wide characters by
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/13537
- fix(core): correct bash @P prompt transformation detection by @pyrytakala in
https://github.com/google-gemini/gemini-cli/pull/13544
- Optimize and improve test coverage for cli/src/config by @megha1188 in
https://github.com/google-gemini/gemini-cli/pull/13485
- Improve code coverage for cli/src/ui/privacy package by @megha1188 in
https://github.com/google-gemini/gemini-cli/pull/13493
- docs: fix typos in source code and documentation by @fancive in
https://github.com/google-gemini/gemini-cli/pull/13577
- Improved code coverage for cli/src/zed-integration by @megha1188 in
https://github.com/google-gemini/gemini-cli/pull/13570
- feat(ui): build interactive session browser component by @bl-ue in
https://github.com/google-gemini/gemini-cli/pull/13351
- Fix multiple bugs with auth flow including using the implemented but unused
restart support. by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/13565
- feat(core): add modelAvailabilityService for managing and tracking model
health by @adamfweidman in
https://github.com/google-gemini/gemini-cli/pull/13426
- docs: fix grammar typo "a MCP" to "an MCP" by @noahacgn in
https://github.com/google-gemini/gemini-cli/pull/13595
- feat: custom loading phrase when interactive shell requires input by
@jackwotherspoon in https://github.com/google-gemini/gemini-cli/pull/12535
- docs: Update uninstall command to reflect multiple extension support by
@JayadityaGit in https://github.com/google-gemini/gemini-cli/pull/13582
- bug(core): Ensure we use thinking budget on fallback to 2.5 by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/13596
- Remove useModelRouter experimental flag by @Adib234 in
https://github.com/google-gemini/gemini-cli/pull/13593
- feat(docs): Ensure multiline JS objects are rendered properly. by @joshualitt
in https://github.com/google-gemini/gemini-cli/pull/13535
- Fix exp id logging by @owenofbrien in
https://github.com/google-gemini/gemini-cli/pull/13430
- Moved client id logging into createBasicLogEvent by @owenofbrien in
https://github.com/google-gemini/gemini-cli/pull/13607
- Restore bracketed paste mode after external editor exit by @scidomino in
https://github.com/google-gemini/gemini-cli/pull/13606
- feat(core): Add support for custom aliases for model configs. by @joshualitt
in https://github.com/google-gemini/gemini-cli/pull/13546
- feat(core): Add `BaseLlmClient.generateContent`. by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/13591
- Turn off alternate buffer mode by default. by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/13623
- fix(cli): Prevent stdout/stderr patching for extension commands by @chrstnb in
https://github.com/google-gemini/gemini-cli/pull/13600
- Improve test coverage for cli/src/ui/components by @megha1188 in
https://github.com/google-gemini/gemini-cli/pull/13598
- Update ink version to 6.4.6 by @jacob314 in
https://github.com/google-gemini/gemini-cli/pull/13631
- chore/release: bump version to 0.19.0-nightly.20251122.42c2e1b21 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13637
- chore/release: bump version to 0.19.0-nightly.20251123.dadd606c0 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13675
- chore/release: bump version to 0.19.0-nightly.20251124.e177314a4 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13713
- fix(core): Fix context window overflow warning for PDF files by @kkitase in
https://github.com/google-gemini/gemini-cli/pull/13548
- feat :rephrasing the extension logging messages to run the explore command
when there are no extensions installed by @JayadityaGit in
https://github.com/google-gemini/gemini-cli/pull/13740
- Improve code coverage for cli package by @megha1188 in
https://github.com/google-gemini/gemini-cli/pull/13724
- Add session subtask in /stats command by @Adib234 in
https://github.com/google-gemini/gemini-cli/pull/13750
- feat(core): Migrate chatCompressionService to model configs. by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/12863
- feat(hooks): Hook Telemetry Infrastructure by @Edilmo in
https://github.com/google-gemini/gemini-cli/pull/9082
- fix: (some minor improvements to configs and getPackageJson return behaviour)
by @grMLEqomlkkU5Eeinz4brIrOVCUCkJuN in
https://github.com/google-gemini/gemini-cli/pull/12510
- feat(hooks): Hook Event Handling by @Edilmo in
https://github.com/google-gemini/gemini-cli/pull/9097
- feat(hooks): Hook Agent Lifecycle Integration by @Edilmo in
https://github.com/google-gemini/gemini-cli/pull/9105
- feat(core): Land bool for alternate system prompt. by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/13764
- bug(core): Add default chat compression config. by @joshualitt in
https://github.com/google-gemini/gemini-cli/pull/13766
- feat(model-availability): introduce ModelPolicy and PolicyCatalog by
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/13751
- feat(hooks): Hook System Orchestration by @Edilmo in
https://github.com/google-gemini/gemini-cli/pull/9102
- feat(config): add isModelAvailabilityServiceEnabled setting by @adamfweidman
in https://github.com/google-gemini/gemini-cli/pull/13777
- chore/release: bump version to 0.19.0-nightly.20251125.f6d97d448 by
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13782
- chore: remove console.error by @adamfweidman in
https://github.com/google-gemini/gemini-cli/pull/13779
- fix: Add $schema property to settings.schema.json by @sacrosanctic in
https://github.com/google-gemini/gemini-cli/pull/12763
- fix(cli): allow non-GitHub SCP-styled URLs for extension installation by @m0ps
in https://github.com/google-gemini/gemini-cli/pull/13800
- fix(resume): allow passing a prompt via stdin while resuming using --resume by
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13520
- feat(sessions): add /resume slash command to open the session browser by
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13621
- docs(sessions): add documentation for chat recording and session management by
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13667
- Fix TypeError: "URL.parse is not a function" for Node.js < v22 by @macarronesc
in https://github.com/google-gemini/gemini-cli/pull/13698
- fallback to flash for TerminalQuota errors by @sehoon38 in
https://github.com/google-gemini/gemini-cli/pull/13791
- Update Code Wiki README badge by @PatoBeltran in
https://github.com/google-gemini/gemini-cli/pull/13768
- Add Databricks auth support and custom header option to gemini cli by
@AarushiShah in https://github.com/google-gemini/gemini-cli/pull/11893
- Update dependency for modelcontextprotocol/sdk to 1.23.0 by @bbiggs in
https://github.com/google-gemini/gemini-cli/pull/13827
**Full changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.23.0-preview.6...v0.24.0-preview.0
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.18.0-preview.4...v0.19.0-preview.0
File diff suppressed because it is too large Load Diff
-27
View File
@@ -73,9 +73,6 @@ Slash commands provide meta-level control over the CLI itself.
- **`/copy`**
- **Description:** Copies the last output produced by Gemini CLI to your
clipboard, for easy sharing or reuse.
- **Behavior:**
- Local sessions use system clipboard tools (pbcopy/xclip/clip).
- Remote sessions (SSH/WSL) use OSC 52 and require terminal support.
- **Note:** This command requires platform-specific clipboard tools to be
installed.
- On Linux, it requires `xclip` or `xsel`. You can typically install them
@@ -167,13 +164,6 @@ Slash commands provide meta-level control over the CLI itself.
- **Note:** Only available if checkpointing is configured via
[settings](../get-started/configuration.md). See
[Checkpointing documentation](../cli/checkpointing.md) for more details.
- [**`/rewind`**](./rewind.md)
- **Description:** Browse and rewind previous interactions. Allows you to
rewind the conversation, revert file changes, or both. Provides an
interactive interface to select the exact point to rewind to.
- **Keyboard shortcut:** Press **Esc** twice.
- **`/resume`**
- **Description:** Browse and resume previous conversation sessions. Opens an
interactive session browser where you can search, filter, and select from
@@ -204,23 +194,6 @@ Slash commands provide meta-level control over the CLI itself.
modify them as desired. Changes to some settings are applied immediately,
while others require a restart.
- [**`/skills`**](./skills.md)
- **Description:** (Experimental) Manage Agent Skills, which provide on-demand
expertise and specialized workflows.
- **Sub-commands:**
- **`list`**:
- **Description:** List all discovered skills and their current status
(enabled/disabled).
- **`enable`**:
- **Description:** Enable a specific skill by name.
- **Usage:** `/skills enable <name>`
- **`disable`**:
- **Description:** Disable a specific skill by name.
- **Usage:** `/skills disable <name>`
- **`reload`**:
- **Description:** Refresh the list of discovered skills from all tiers
(workspace, user, and extensions).
- **`/stats`**
- **Description:** Display detailed statistics for the current Gemini CLI
session, including token usage, cached token savings (when available), and
+780
View File
@@ -0,0 +1,780 @@
# Gemini CLI configuration
Gemini CLI offers several ways to configure its behavior, including environment
variables, command-line arguments, and settings files. This document outlines
the different configuration methods and available settings.
## Configuration layers
Configuration is applied in the following order of precedence (lower numbers are
overridden by higher numbers):
1. **Default values:** Hardcoded defaults within the application.
2. **User settings file:** Global settings for the current user.
3. **Project settings file:** Project-specific settings.
4. **System settings file:** System-wide settings.
5. **Environment variables:** System-wide or session-specific variables,
potentially loaded from `.env` files.
6. **Command-line arguments:** Values passed when launching the CLI.
## Settings files
Gemini CLI uses `settings.json` files for persistent configuration. There are
three locations for these files:
- **User settings file:**
- **Location:** `~/.gemini/settings.json` (where `~` is your home directory).
- **Scope:** Applies to all Gemini CLI sessions for the current user.
- **Project settings file:**
- **Location:** `.gemini/settings.json` within your project's root directory.
- **Scope:** Applies only when running Gemini CLI from that specific project.
Project settings override user settings.
- **System settings file:**
- **Location:** `/etc/gemini-cli/settings.json` (Linux),
`C:\ProgramData\gemini-cli\settings.json` (Windows) or
`/Library/Application Support/GeminiCli/settings.json` (macOS). The path can
be overridden using the `GEMINI_CLI_SYSTEM_SETTINGS_PATH` environment
variable.
- **Scope:** Applies to all Gemini CLI sessions on the system, for all users.
System settings override user and project settings. May be useful for system
administrators at enterprises to have controls over users' Gemini CLI
setups.
**Note on environment variables in settings:** String values within your
`settings.json` files can reference environment variables using either
`$VAR_NAME` or `${VAR_NAME}` syntax. These variables will be automatically
resolved when the settings are loaded. For example, if you have an environment
variable `MY_API_TOKEN`, you could use it in `settings.json` like this:
`"apiKey": "$MY_API_TOKEN"`.
### The `.gemini` directory in your project
In addition to a project settings file, a project's `.gemini` directory can
contain other project-specific files related to Gemini CLI's operation, such as:
- [Custom sandbox profiles](#sandboxing) (e.g.,
`.gemini/sandbox-macos-custom.sb`, `.gemini/sandbox.Dockerfile`).
### Available settings in `settings.json`:
- **`contextFileName`** (string or array of strings):
- **Description:** Specifies the filename for context files (e.g.,
`GEMINI.md`, `AGENTS.md`). Can be a single filename or a list of accepted
filenames.
- **Default:** `GEMINI.md`
- **Example:** `"contextFileName": "AGENTS.md"`
- **`bugCommand`** (object):
- **Description:** Overrides the default URL for the `/bug` command.
- **Default:**
`"urlTemplate": "https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.yml&title={title}&info={info}"`
- **Properties:**
- **`urlTemplate`** (string): A URL that can contain `{title}` and `{info}`
placeholders.
- **Example:**
```json
"bugCommand": {
"urlTemplate": "https://bug.example.com/new?title={title}&info={info}"
}
```
- **`fileFiltering`** (object):
- **Description:** Controls git-aware file filtering behavior for @ commands
and file discovery tools.
- **Default:** `"respectGitIgnore": true, "enableRecursiveFileSearch": true`
- **Properties:**
- **`respectGitIgnore`** (boolean): Whether to respect .gitignore patterns
when discovering files. When set to `true`, git-ignored files (like
`node_modules/`, `dist/`, `.env`) are automatically excluded from @
commands and file listing operations.
- **`enableRecursiveFileSearch`** (boolean): Whether to enable searching
recursively for filenames under the current tree when completing @
prefixes in the prompt.
- **Example:**
```json
"fileFiltering": {
"respectGitIgnore": true,
"enableRecursiveFileSearch": false
}
```
- **`coreTools`** (array of strings):
- **Description:** Allows you to specify a list of core tool names that should
be made available to the model. This can be used to restrict the set of
built-in tools. See [Built-in Tools](../core/tools-api.md#built-in-tools)
for a list of core tools. You can also specify command-specific restrictions
for tools that support it, like the `ShellTool`. For example,
`"coreTools": ["ShellTool(ls -l)"]` will only allow the `ls -l` command to
be executed.
- **Default:** All tools available for use by the Gemini model.
- **Example:** `"coreTools": ["ReadFileTool", "GlobTool", "ShellTool(ls)"]`.
- **`excludeTools`** (array of strings):
- **Description:** Allows you to specify a list of core tool names that should
be excluded from the model. A tool listed in both `excludeTools` and
`coreTools` is excluded. You can also specify command-specific restrictions
for tools that support it, like the `ShellTool`. For example,
`"excludeTools": ["ShellTool(rm -rf)"]` will block the `rm -rf` command.
- **Default**: No tools excluded.
- **Example:** `"excludeTools": ["run_shell_command", "findFiles"]`.
- **Security Note:** Command-specific restrictions in `excludeTools` for
`run_shell_command` are based on simple string matching and can be easily
bypassed. This feature is **not a security mechanism** and should not be
relied upon to safely execute untrusted code. It is recommended to use
`coreTools` to explicitly select commands that can be executed.
- **`allowMCPServers`** (array of strings):
- **Description:** Allows you to specify a list of MCP server names that
should be made available to the model. This can be used to restrict the set
of MCP servers to connect to. Note that this will be ignored if
`--allowed-mcp-server-names` is set.
- **Default:** All MCP servers are available for use by the Gemini model.
- **Example:** `"allowMCPServers": ["myPythonServer"]`.
- **Security Note:** This uses simple string matching on MCP server names,
which can be modified. If you're a system administrator looking to prevent
users from bypassing this, consider configuring the `mcpServers` at the
system settings level such that the user will not be able to configure any
MCP servers of their own. This should not be used as an airtight security
mechanism.
- **`excludeMCPServers`** (array of strings):
- **Description:** Allows you to specify a list of MCP server names that
should be excluded from the model. A server listed in both
`excludeMCPServers` and `allowMCPServers` is excluded. Note that this will
be ignored if `--allowed-mcp-server-names` is set.
- **Default**: No MCP servers excluded.
- **Example:** `"excludeMCPServers": ["myNodeServer"]`.
- **Security note:** This uses simple string matching on MCP server names,
which can be modified. If you're a system administrator looking to prevent
users from bypassing this, consider configuring the `mcpServers` at the
system settings level such that the user will not be able to configure any
MCP servers of their own. This should not be used as an airtight security
mechanism.
- **`autoAccept`** (boolean):
- **Description:** Controls whether the CLI automatically accepts and executes
tool calls that are considered safe (e.g., read-only operations) without
explicit user confirmation. If set to `true`, the CLI will bypass the
confirmation prompt for tools deemed safe.
- **Default:** `false`
- **Example:** `"autoAccept": true`
- **`theme`** (string):
- **Description:** Sets the visual [theme](./themes.md) for Gemini CLI.
- **Default:** `"Default"`
- **Example:** `"theme": "GitHub"`
- **`vimMode`** (boolean):
- **Description:** Enables or disables vim mode for input editing. When
enabled, the input area supports vim-style navigation and editing commands
with NORMAL and INSERT modes. The vim mode status is displayed in the footer
and persists between sessions.
- **Default:** `false`
- **Example:** `"vimMode": true`
- **`sandbox`** (boolean or string):
- **Description:** Controls whether and how to use sandboxing for tool
execution. If set to `true`, Gemini CLI uses a pre-built
`gemini-cli-sandbox` Docker image. For more information, see
[Sandboxing](#sandboxing).
- **Default:** `false`
- **Example:** `"sandbox": "docker"`
- **`toolDiscoveryCommand`** (string):
- **Description:** Defines a custom shell command for discovering tools from
your project. The shell command must return on `stdout` a JSON array of
[function declarations](https://ai.google.dev/gemini-api/docs/function-calling#function-declarations).
Tool wrappers are optional.
- **Default:** Empty
- **Example:** `"toolDiscoveryCommand": "bin/get_tools"`
- **`toolCallCommand`** (string):
- **Description:** Defines a custom shell command for calling a specific tool
that was discovered using `toolDiscoveryCommand`. The shell command must
meet the following criteria:
- It must take function `name` (exactly as in
[function declaration](https://ai.google.dev/gemini-api/docs/function-calling#function-declarations))
as first command line argument.
- It must read function arguments as JSON on `stdin`, analogous to
[`functionCall.args`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#functioncall).
- It must return function output as JSON on `stdout`, analogous to
[`functionResponse.response.content`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#functionresponse).
- **Default:** Empty
- **Example:** `"toolCallCommand": "bin/call_tool"`
- **`mcpServers`** (object):
- **Description:** Configures connections to one or more Model-Context
Protocol (MCP) servers for discovering and using custom tools. Gemini CLI
attempts to connect to each configured MCP server to discover available
tools. If multiple MCP servers expose a tool with the same name, the tool
names will be prefixed with the server alias you defined in the
configuration (e.g., `serverAlias__actualToolName`) to avoid conflicts. Note
that the system might strip certain schema properties from MCP tool
definitions for compatibility.
- **Default:** Empty
- **Properties:**
- **`<SERVER_NAME>`** (object): The server parameters for the named server.
- `command` (string, required): The command to execute to start the MCP
server.
- `args` (array of strings, optional): Arguments to pass to the command.
- `env` (object, optional): Environment variables to set for the server
process.
- `cwd` (string, optional): The working directory in which to start the
server.
- `timeout` (number, optional): Timeout in milliseconds for requests to
this MCP server.
- `trust` (boolean, optional): Trust this server and bypass all tool call
confirmations.
- `includeTools` (array of strings, optional): List of tool names to
include from this MCP server. When specified, only the tools listed here
will be available from this server (whitelist behavior). If not
specified, all tools from the server are enabled by default.
- `excludeTools` (array of strings, optional): List of tool names to
exclude from this MCP server. Tools listed here will not be available to
the model, even if they are exposed by the server. **Note:**
`excludeTools` takes precedence over `includeTools` - if a tool is in
both lists, it will be excluded.
- **Example:**
```json
"mcpServers": {
"myPythonServer": {
"command": "python",
"args": ["mcp_server.py", "--port", "8080"],
"cwd": "./mcp_tools/python",
"timeout": 5000,
"includeTools": ["safe_tool", "file_reader"],
},
"myNodeServer": {
"command": "node",
"args": ["mcp_server.js"],
"cwd": "./mcp_tools/node",
"excludeTools": ["dangerous_tool", "file_deleter"]
},
"myDockerServer": {
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "API_KEY", "ghcr.io/foo/bar"],
"env": {
"API_KEY": "$MY_API_TOKEN"
}
}
}
```
- **`checkpointing`** (object):
- **Description:** Configures the checkpointing feature, which allows you to
save and restore conversation and file states. See the
[Checkpointing documentation](./checkpointing.md) for more details.
- **Default:** `{"enabled": false}`
- **Properties:**
- **`enabled`** (boolean): When `true`, the `/restore` command is available.
- **`preferredEditor`** (string):
- **Description:** Specifies the preferred editor to use for viewing diffs.
- **Default:** `vscode`
- **Example:** `"preferredEditor": "vscode"`
- **`telemetry`** (object)
- **Description:** Configures logging and metrics collection for Gemini CLI.
For more information, see [Telemetry](./telemetry.md).
- **Default:**
`{"enabled": false, "target": "local", "otlpEndpoint": "http://localhost:4317", "logPrompts": true}`
- **Properties:**
- **`enabled`** (boolean): Whether or not telemetry is enabled.
- **`target`** (string): The destination for collected telemetry. Supported
values are `local` and `gcp`.
- **`otlpEndpoint`** (string): The endpoint for the OTLP Exporter.
- **`logPrompts`** (boolean): Whether or not to include the content of user
prompts in the logs.
- **Example:**
```json
"telemetry": {
"enabled": true,
"target": "local",
"otlpEndpoint": "http://localhost:16686",
"logPrompts": false
}
```
- **`usageStatisticsEnabled`** (boolean):
- **Description:** Enables or disables the collection of usage statistics. See
[Usage Statistics](#usage-statistics) for more information.
- **Default:** `true`
- **Example:**
```json
"usageStatisticsEnabled": false
```
- **`hideTips`** (boolean):
- **Description:** Enables or disables helpful tips in the CLI interface.
- **Default:** `false`
- **Example:**
```json
"hideTips": true
```
- **`hideBanner`** (boolean):
- **Description:** Enables or disables the startup banner (ASCII art logo) in
the CLI interface.
- **Default:** `false`
- **Example:**
```json
"hideBanner": true
```
- **`maxSessionTurns`** (number):
- **Description:** Sets the maximum number of turns for a session. If the
session exceeds this limit, the CLI will stop processing and start a new
chat.
- **Default:** `-1` (unlimited)
- **Example:**
```json
"maxSessionTurns": 10
```
- **`summarizeToolOutput`** (object):
- **Description:** Enables or disables the summarization of tool output. You
can specify the token budget for the summarization using the `tokenBudget`
setting.
- Note: Currently only the `run_shell_command` tool is supported.
- **Default:** `{}` (Disabled by default)
- **Example:**
```json
"summarizeToolOutput": {
"run_shell_command": {
"tokenBudget": 2000
}
}
```
- **`excludedProjectEnvVars`** (array of strings):
- **Description:** Specifies environment variables that should be excluded
from being loaded from project `.env` files. This prevents project-specific
environment variables (like `DEBUG=true`) from interfering with gemini-cli
behavior. Variables from `.gemini/.env` files are never excluded.
- **Default:** `["DEBUG", "DEBUG_MODE"]`
- **Example:**
```json
"excludedProjectEnvVars": ["DEBUG", "DEBUG_MODE", "NODE_ENV"]
```
- **`includeDirectories`** (array of strings):
- **Description:** Specifies an array of additional absolute or relative paths
to include in the workspace context. This allows you to work with files
across multiple directories as if they were one. Paths can use `~` to refer
to the user's home directory. This setting can be combined with the
`--include-directories` command-line flag.
- **Default:** `[]`
- **Example:**
```json
"includeDirectories": [
"/path/to/another/project",
"../shared-library",
"~/common-utils"
]
```
- **`loadMemoryFromIncludeDirectories`** (boolean):
- **Description:** Controls the behavior of the `/memory refresh` command. If
set to `true`, `GEMINI.md` files should be loaded from all directories that
are added. If set to `false`, `GEMINI.md` should only be loaded from the
current directory.
- **Default:** `false`
- **Example:**
```json
"loadMemoryFromIncludeDirectories": true
```
### Example `settings.json`:
```json
{
"theme": "GitHub",
"sandbox": "docker",
"toolDiscoveryCommand": "bin/get_tools",
"toolCallCommand": "bin/call_tool",
"mcpServers": {
"mainServer": {
"command": "bin/mcp_server.py"
},
"anotherServer": {
"command": "node",
"args": ["mcp_server.js", "--verbose"]
}
},
"telemetry": {
"enabled": true,
"target": "local",
"otlpEndpoint": "http://localhost:4317",
"logPrompts": true
},
"usageStatisticsEnabled": true,
"hideTips": false,
"hideBanner": false,
"maxSessionTurns": 10,
"summarizeToolOutput": {
"run_shell_command": {
"tokenBudget": 100
}
},
"excludedProjectEnvVars": ["DEBUG", "DEBUG_MODE", "NODE_ENV"],
"includeDirectories": ["path/to/dir1", "~/path/to/dir2", "../path/to/dir3"],
"loadMemoryFromIncludeDirectories": true
}
```
## Shell history
The CLI keeps a history of shell commands you run. To avoid conflicts between
different projects, this history is stored in a project-specific directory
within your user's home folder.
- **Location:** `~/.gemini/tmp/<project_hash>/shell_history`
- `<project_hash>` is a unique identifier generated from your project's root
path.
- The history is stored in a file named `shell_history`.
## Environment variables and `.env` files
Environment variables are a common way to configure applications, especially for
sensitive information like API keys or for settings that might change between
environments.
The CLI automatically loads environment variables from an `.env` file. The
loading order is:
1. `.env` file in the current working directory.
2. If not found, it searches upwards in parent directories until it finds an
`.env` file or reaches the project root (identified by a `.git` folder) or
the home directory.
3. If still not found, it looks for `~/.env` (in the user's home directory).
**Environment variable exclusion:** Some environment variables (like `DEBUG` and
`DEBUG_MODE`) are automatically excluded from being loaded from project `.env`
files to prevent interference with gemini-cli behavior. Variables from
`.gemini/.env` files are never excluded. You can customize this behavior using
the `excludedProjectEnvVars` setting in your `settings.json` file.
- **`GEMINI_API_KEY`** (Required):
- Your API key for the Gemini API.
- **Crucial for operation.** The CLI will not function without it.
- Set this in your shell profile (e.g., `~/.bashrc`, `~/.zshrc`) or an `.env`
file.
- **`GEMINI_MODEL`**:
- Specifies the default Gemini model to use.
- Overrides the hardcoded default
- Example: `export GEMINI_MODEL="gemini-2.5-flash"`
- **`GEMINI_CLI_CUSTOM_HEADERS`**:
- Adds extra HTTP headers to Gemini API and Code Assist requests.
- Accepts a comma-separated list of `Name: value` pairs.
- Example:
`export GEMINI_CLI_CUSTOM_HEADERS="X-My-Header: foo, X-Trace-ID: abc123"`.
- **`GEMINI_API_KEY_AUTH_MECHANISM`**:
- Specifies how the API key should be sent for authentication when using
`AuthType.USE_GEMINI` or `AuthType.USE_VERTEX_AI`.
- Valid values are `x-goog-api-key` (default) or `bearer`.
- If set to `bearer`, the API key will be sent in the
`Authorization: Bearer <key>` header.
- Example: `export GEMINI_API_KEY_AUTH_MECHANISM="bearer"`
- **`GOOGLE_API_KEY`**:
- Your Google Cloud API key.
- Required for using Vertex AI in express mode.
- Ensure you have the necessary permissions.
- Example: `export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"`.
- **`GOOGLE_CLOUD_PROJECT`**:
- Your Google Cloud Project ID.
- Required for using Code Assist or Vertex AI.
- If using Vertex AI, ensure you have the necessary permissions in this
project.
- **Cloud shell note:** When running in a Cloud Shell environment, this
variable defaults to a special project allocated for Cloud Shell users. If
you have `GOOGLE_CLOUD_PROJECT` set in your global environment in Cloud
Shell, it will be overridden by this default. To use a different project in
Cloud Shell, you must define `GOOGLE_CLOUD_PROJECT` in a `.env` file.
- Example: `export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`.
- **`GOOGLE_APPLICATION_CREDENTIALS`** (string):
- **Description:** The path to your Google Application Credentials JSON file.
- **Example:**
`export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/credentials.json"`
- **`OTLP_GOOGLE_CLOUD_PROJECT`**:
- Your Google Cloud Project ID for Telemetry in Google Cloud
- Example: `export OTLP_GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`.
- **`GOOGLE_CLOUD_LOCATION`**:
- Your Google Cloud Project Location (e.g., us-central1).
- Required for using Vertex AI in non express mode.
- Example: `export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"`.
- **`GEMINI_SANDBOX`**:
- Alternative to the `sandbox` setting in `settings.json`.
- Accepts `true`, `false`, `docker`, `podman`, or a custom command string.
- **`HTTP_PROXY` / `HTTPS_PROXY`**:
- Specifies the proxy server to use for outgoing HTTP/HTTPS requests.
- Example: `export HTTPS_PROXY="http://proxy.example.com:8080"`
- **`SEATBELT_PROFILE`** (macOS specific):
- Switches the Seatbelt (`sandbox-exec`) profile on macOS.
- `permissive-open`: (Default) Restricts writes to the project folder (and a
few other folders, see
`packages/cli/src/utils/sandbox-macos-permissive-open.sb`) but allows other
operations.
- `strict`: Uses a strict profile that declines operations by default.
- `<profile_name>`: Uses a custom profile. To define a custom profile, create
a file named `sandbox-macos-<profile_name>.sb` in your project's `.gemini/`
directory (e.g., `my-project/.gemini/sandbox-macos-custom.sb`).
- **`DEBUG` or `DEBUG_MODE`** (often used by underlying libraries or the CLI
itself):
- Set to `true` or `1` to enable verbose debug logging, which can be helpful
for troubleshooting.
- **Note:** These variables are automatically excluded from project `.env`
files by default to prevent interference with gemini-cli behavior. Use
`.gemini/.env` files if you need to set these for gemini-cli specifically.
- **`NO_COLOR`**:
- Set to any value to disable all color output in the CLI.
- **`CLI_TITLE`**:
- Set to a string to customize the title of the CLI.
- **`CODE_ASSIST_ENDPOINT`**:
- Specifies the endpoint for the code assist server.
- This is useful for development and testing.
- **`GEMINI_SYSTEM_MD`**:
- Overrides the base system prompt with the contents of a Markdown file.
- If set to `1` or `true`, it uses the file at `.gemini/system.md`.
- If set to a file path, it uses that file. The path can be absolute or
relative. `~` is supported for the home directory.
- The specified file must exist.
- **`GEMINI_WRITE_SYSTEM_MD`**:
- Writes the default system prompt to a file. This is useful for getting a
template to customize.
- If set to `1` or `true`, it writes to `.gemini/system.md`.
- If set to a file path, it writes to that path. The path can be absolute or
relative. `~` is supported for the home directory. **Note: This will
overwrite the file if it already exists.**
## Command-line arguments
Arguments passed directly when running the CLI can override other configurations
for that specific session.
- **`--model <model_name>`** (**`-m <model_name>`**):
- Specifies the Gemini model to use for this session.
- Example: `npm start -- --model gemini-1.5-pro-latest`
- **`--prompt <your_prompt>`** (**`-p <your_prompt>`**):
- Used to pass a prompt directly to the command. This invokes Gemini CLI in a
non-interactive mode.
- **`--prompt-interactive <your_prompt>`** (**`-i <your_prompt>`**):
- Starts an interactive session with the provided prompt as the initial input.
- The prompt is processed within the interactive session, not before it.
- Cannot be used when piping input from stdin.
- Example: `gemini -i "explain this code"`
- **`--sandbox`** (**`-s`**):
- Enables sandbox mode for this session.
- **`--sandbox-image`**:
- Sets the sandbox image URI.
- **`--debug`** (**`-d`**):
- Enables debug mode for this session, providing more verbose output.
- **`--all-files`** (**`-a`**):
- If set, recursively includes all files within the current directory as
context for the prompt.
- **`--help`** (or **`-h`**):
- Displays help information about command-line arguments.
- **`--show-memory-usage`**:
- Displays the current memory usage.
- **`--yolo`**:
- Enables YOLO mode, which automatically approves all tool calls.
- **`--telemetry`**:
- Enables [telemetry](./telemetry.md).
- **`--telemetry-target`**:
- Sets the telemetry target. See [telemetry](./telemetry.md) for more
information.
- **`--telemetry-otlp-endpoint`**:
- Sets the OTLP endpoint for telemetry. See [telemetry](./telemetry.md) for
more information.
- **`--telemetry-log-prompts`**:
- Enables logging of prompts for telemetry. See [telemetry](./telemetry.md)
for more information.
- **`--extensions <extension_name ...>`** (**`-e <extension_name ...>`**):
- Specifies a list of extensions to use for the session. If not provided, all
available extensions are used.
- Use the special term `gemini -e none` to disable all extensions.
- Example: `gemini -e my-extension -e my-other-extension`
- **`--list-extensions`** (**`-l`**):
- Lists all available extensions and exits.
- **`--include-directories <dir1,dir2,...>`**:
- Includes additional directories in the workspace for multi-directory
support.
- Can be specified multiple times or as comma-separated values.
- 5 directories can be added at maximum.
- Example: `--include-directories /path/to/project1,/path/to/project2` or
`--include-directories /path/to/project1 --include-directories /path/to/project2`
- **`--version`**:
- Displays the version of the CLI.
## Context files (hierarchical instructional context)
While not strictly configuration for the CLI's _behavior_, context files
(defaulting to `GEMINI.md` but configurable via the `contextFileName` setting)
are crucial for configuring the _instructional context_ (also referred to as
"memory") provided to the Gemini model. This powerful feature allows you to give
project-specific instructions, coding style guides, or any relevant background
information to the AI, making its responses more tailored and accurate to your
needs. The CLI includes UI elements, such as an indicator in the footer showing
the number of loaded context files, to keep you informed about the active
context.
- **Purpose:** These Markdown files contain instructions, guidelines, or context
that you want the Gemini model to be aware of during your interactions. The
system is designed to manage this instructional context hierarchically.
### Example context file content (e.g., `GEMINI.md`)
Here's a conceptual example of what a context file at the root of a TypeScript
project might contain:
```markdown
# Project: My Awesome TypeScript Library
## General Instructions:
- When generating new TypeScript code, please follow the existing coding style.
- Ensure all new functions and classes have JSDoc comments.
- Prefer functional programming paradigms where appropriate.
- All code should be compatible with TypeScript 5.0 and Node.js 20+.
## Coding Style:
- Use 2 spaces for indentation.
- Interface names should be prefixed with `I` (e.g., `IUserService`).
- Private class members should be prefixed with an underscore (`_`).
- Always use strict equality (`===` and `!==`).
## Specific Component: `src/api/client.ts`
- This file handles all outbound API requests.
- When adding new API call functions, ensure they include robust error handling
and logging.
- Use the existing `fetchWithRetry` utility for all GET requests.
## Regarding Dependencies:
- Avoid introducing new external dependencies unless absolutely necessary.
- If a new dependency is required, please state the reason.
```
This example demonstrates how you can provide general project context, specific
coding conventions, and even notes about particular files or components. The
more relevant and precise your context files are, the better the AI can assist
you. Project-specific context files are highly encouraged to establish
conventions and context.
- **Hierarchical loading and precedence:** The CLI implements a sophisticated
hierarchical memory system by loading context files (e.g., `GEMINI.md`) from
several locations. Content from files lower in this list (more specific)
typically overrides or supplements content from files higher up (more
general). The exact concatenation order and final context can be inspected
using the `/memory show` command. The typical loading order is:
1. **Global context file:**
- Location: `~/.gemini/<contextFileName>` (e.g., `~/.gemini/GEMINI.md` in
your user home directory).
- Scope: Provides default instructions for all your projects.
2. **Project root and ancestors context files:**
- Location: The CLI searches for the configured context file in the
current working directory and then in each parent directory up to either
the project root (identified by a `.git` folder) or your home directory.
- Scope: Provides context relevant to the entire project or a significant
portion of it.
3. **Sub-directory context files (contextual/local):**
- Location: The CLI also scans for the configured context file in
subdirectories _below_ the current working directory (respecting common
ignore patterns like `node_modules`, `.git`, etc.). The breadth of this
search is limited to 200 directories by default, but can be configured
with a `memoryDiscoveryMaxDirs` field in your `settings.json` file.
- Scope: Allows for highly specific instructions relevant to a particular
component, module, or subsection of your project.
- **Concatenation and UI indication:** The contents of all found context files
are concatenated (with separators indicating their origin and path) and
provided as part of the system prompt to the Gemini model. The CLI footer
displays the count of loaded context files, giving you a quick visual cue
about the active instructional context.
- **Importing content:** You can modularize your context files by importing
other Markdown files using the `@path/to/file.md` syntax. For more details,
see the [Memory Import Processor documentation](../core/memport.md).
- **Commands for memory management:**
- Use `/memory refresh` to force a re-scan and reload of all context files
from all configured locations. This updates the AI's instructional context.
- Use `/memory show` to display the combined instructional context currently
loaded, allowing you to verify the hierarchy and content being used by the
AI.
- See the [Commands documentation](./commands.md#memory) for full details on
the `/memory` command and its sub-commands (`show` and `refresh`).
By understanding and utilizing these configuration layers and the hierarchical
nature of context files, you can effectively manage the AI's memory and tailor
the Gemini CLI's responses to your specific needs and projects.
## Sandboxing
The Gemini CLI can execute potentially unsafe operations (like shell commands
and file modifications) within a sandboxed environment to protect your system.
Sandboxing is disabled by default, but you can enable it in a few ways:
- Using `--sandbox` or `-s` flag.
- Setting `GEMINI_SANDBOX` environment variable.
- Sandbox is enabled in `--yolo` mode by default.
By default, it uses a pre-built `gemini-cli-sandbox` Docker image.
For project-specific sandboxing needs, you can create a custom Dockerfile at
`.gemini/sandbox.Dockerfile` in your project's root directory. This Dockerfile
can be based on the base sandbox image:
```dockerfile
FROM gemini-cli-sandbox
# Add your custom dependencies or configurations here
# For example:
# RUN apt-get update && apt-get install -y some-package
# COPY ./my-config /app/my-config
```
When `.gemini/sandbox.Dockerfile` exists, you can use `BUILD_SANDBOX`
environment variable when running Gemini CLI to automatically build the custom
sandbox image:
```bash
BUILD_SANDBOX=1 gemini -s
```
## Usage statistics
To help us improve the Gemini CLI, we collect anonymized usage statistics. This
data helps us understand how the CLI is used, identify common issues, and
prioritize new features.
**What we collect:**
- **Tool calls:** We log the names of the tools that are called, whether they
succeed or fail, and how long they take to execute. We do not collect the
arguments passed to the tools or any data returned by them.
- **API requests:** We log the Gemini model used for each request, the duration
of the request, and whether it was successful. We do not collect the content
of the prompts or responses.
- **Session information:** We collect information about the configuration of the
CLI, such as the enabled tools and the approval mode.
**What we DON'T collect:**
- **Personally identifiable information (PII):** We do not collect any personal
information, such as your name, email address, or API keys.
- **Prompt and response content:** We do not log the content of your prompts or
the responses from the Gemini model.
- **File content:** We do not log the content of any files that are read or
written by the CLI.
**How to opt out:**
You can opt out of usage statistics collection at any time by setting the
`usageStatisticsEnabled` property to `false` in your `settings.json` file:
```json
{
"usageStatisticsEnabled": false
}
```
+9 -9
View File
@@ -50,7 +50,7 @@ Your command definition files must be written in the TOML format and use the
## Handling arguments
Custom commands support two powerful methods for handling arguments. The CLI
automatically chooses the correct method based on the content of your command's
automatically chooses the correct method based on the content of your command\'s
`prompt`.
### 1. Context-aware injection with `{{args}}`
@@ -96,13 +96,13 @@ Search Results:
"""
```
When you run `/grep-code It's complicated`:
When you run `/grep-code It\'s complicated`:
1. The CLI sees `{{args}}` used both outside and inside `!{...}`.
2. Outside: The first `{{args}}` is replaced raw with `It's complicated`.
2. Outside: The first `{{args}}` is replaced raw with `It\'s complicated`.
3. Inside: The second `{{args}}` is replaced with the escaped version (e.g., on
Linux: `"It\'s complicated"`).
4. The command executed is `grep -r "It's complicated" .`.
4. The command executed is `grep -r "It\'s complicated" .`.
5. The CLI prompts you to confirm this exact, secure command before execution.
6. The final prompt is sent.
@@ -129,13 +129,13 @@ format and behavior.
# In: <project>/.gemini/commands/changelog.toml
# Invoked via: /changelog 1.2.0 added "Support for default argument parsing."
description = "Adds a new entry to the project's CHANGELOG.md file."
description = "Adds a new entry to the project\'s CHANGELOG.md file."
prompt = """
# Task: Update Changelog
You are an expert maintainer of this software project. A user has invoked a command to add a new entry to the changelog.
**The user's raw command is appended below your instructions.**
**The user\'s raw command is appended below your instructions.**
Your task is to parse the `<version>`, `<change_type>`, and `<message>` from their input and use the `write_file` tool to correctly update the `CHANGELOG.md` file.
@@ -147,7 +147,7 @@ The command follows this format: `/changelog <version> <type> <message>`
1. Read the `CHANGELOG.md` file.
2. Find the section for the specified `<version>`.
3. Add the `<message>` under the correct `<type>` heading.
4. If the version or type section doesn't exist, create it.
4. If the version or type section doesn\'t exist, create it.
5. Adhere strictly to the "Keep a Changelog" format.
"""
```
@@ -241,7 +241,7 @@ operate on specific files.
**Example (`review.toml`):**
This command injects the content of a _fixed_ best practices file
(`docs/best-practices.md`) and uses the user's arguments to provide context for
(`docs/best-practices.md`) and uses the user\'s arguments to provide context for
the review.
```toml
@@ -293,7 +293,7 @@ practice.
description = "Asks the model to refactor the current context into a pure function."
prompt = """
Please analyze the code I've provided in the current context.
Please analyze the code I\'ve provided in the current context.
Refactor it into a pure function.
Your response should include:
+2 -6
View File
@@ -25,12 +25,10 @@ overview of Gemini CLI, see the [main documentation page](../index.md).
- **[Checkpointing](./checkpointing.md):** Automatically save and restore
snapshots of your session and files.
- **[Enterprise configuration](./enterprise.md):** Deploy and manage Gemini CLI
in an enterprise environment.
- **[Enterprise configuration](./enterprise.md):** Deploying and manage Gemini
CLI in an enterprise environment.
- **[Sandboxing](./sandbox.md):** Isolate tool execution in a secure,
containerized environment.
- **[Agent Skills](./skills.md):** (Experimental) Extend the CLI with
specialized expertise and procedural workflows.
- **[Telemetry](./telemetry.md):** Configure observability to monitor usage and
performance.
- **[Token caching](./token-caching.md):** Optimize API costs by caching tokens.
@@ -40,8 +38,6 @@ overview of Gemini CLI, see the [main documentation page](../index.md).
files and directories from being accessed by tools.
- **[Context files (GEMINI.md)](./gemini-md.md):** Provide persistent,
hierarchical context to the model.
- **[System prompt override](./system-prompt.md):** Replace the builtin system
instructions using `GEMINI_SYSTEM_MD`.
## Non-interactive mode
+70 -51
View File
@@ -8,39 +8,32 @@ available combinations.
#### Basic Controls
| Action | Keys |
| --------------------------------------------------------------- | ---------- |
| Confirm the current selection or choice. | `Enter` |
| Dismiss dialogs or cancel the current focus. | `Esc` |
| Cancel the current request or quit the CLI when input is empty. | `Ctrl + C` |
| Exit the CLI when the input buffer is empty. | `Ctrl + D` |
| Action | Keys |
| -------------------------------------------- | ------- |
| Confirm the current selection or choice. | `Enter` |
| Dismiss dialogs or cancel the current focus. | `Esc` |
#### Cursor Movement
| Action | Keys |
| ------------------------------------------- | ------------------------------------------------------------ |
| Move the cursor to the start of the line. | `Ctrl + A`<br />`Home` |
| Move the cursor to the end of the line. | `Ctrl + E`<br />`End` |
| Move the cursor up one line. | `Up Arrow (no Ctrl, no Cmd)` |
| Move the cursor down one line. | `Down Arrow (no Ctrl, no Cmd)` |
| Move the cursor one character to the left. | `Left Arrow (no Ctrl, no Cmd)`<br />`Ctrl + B` |
| Move the cursor one character to the right. | `Right Arrow (no Ctrl, no Cmd)`<br />`Ctrl + F` |
| Move the cursor one word to the left. | `Ctrl + Left Arrow`<br />`Cmd + Left Arrow`<br />`Cmd + B` |
| Move the cursor one word to the right. | `Ctrl + Right Arrow`<br />`Cmd + Right Arrow`<br />`Cmd + F` |
| Action | Keys |
| ----------------------------------------- | ---------------------- |
| Move the cursor to the start of the line. | `Ctrl + A`<br />`Home` |
| Move the cursor to the end of the line. | `Ctrl + E`<br />`End` |
#### Editing
| Action | Keys |
| ------------------------------------------------ | --------------------------------------------------------- |
| Delete from the cursor to the end of the line. | `Ctrl + K` |
| Delete from the cursor to the start of the line. | `Ctrl + U` |
| Clear all text in the input field. | `Ctrl + C` |
| Delete the previous word. | `Ctrl + Backspace`<br />`Cmd + Backspace`<br />`Ctrl + W` |
| Delete the next word. | `Ctrl + Delete`<br />`Cmd + Delete` |
| Delete the character to the left. | `Backspace`<br />`Ctrl + H` |
| Delete the character to the right. | `Delete`<br />`Ctrl + D` |
| Undo the most recent text edit. | `Ctrl + Z (no Shift)` |
| Redo the most recent undone text edit. | `Ctrl + Shift + Z` |
| Action | Keys |
| ------------------------------------------------ | ----------------------------------------- |
| Delete from the cursor to the end of the line. | `Ctrl + K` |
| Delete from the cursor to the start of the line. | `Ctrl + U` |
| Clear all text in the input field. | `Ctrl + C` |
| Delete the previous word. | `Ctrl + Backspace`<br />`Cmd + Backspace` |
#### Screen Control
| Action | Keys |
| -------------------------------------------- | ---------- |
| Clear the terminal screen and redraw the UI. | `Ctrl + L` |
#### Scrolling
@@ -60,7 +53,7 @@ available combinations.
| Show the previous entry in history. | `Ctrl + P (no Shift)` |
| Show the next entry in history. | `Ctrl + N (no Shift)` |
| Start reverse search through history. | `Ctrl + R` |
| Submit the selected reverse-search match. | `Enter (no Ctrl)` |
| Insert the selected reverse-search match. | `Enter (no Ctrl)` |
| Accept a suggestion while reverse searching. | `Tab` |
#### Navigation
@@ -84,40 +77,66 @@ available combinations.
#### Text Input
| Action | Keys |
| ---------------------------------------------- | ---------------------------------------------------------------------- |
| Submit the current prompt. | `Enter (no Ctrl, no Shift, no Cmd)` |
| Insert a newline without submitting. | `Ctrl + Enter`<br />`Cmd + Enter`<br />`Shift + Enter`<br />`Ctrl + J` |
| Open the current prompt in an external editor. | `Ctrl + X` |
| Paste from the clipboard. | `Ctrl + V`<br />`Cmd + V` |
| Action | Keys |
| ------------------------------------ | ------------------------------------------------------------------------------------------- |
| Submit the current prompt. | `Enter (no Ctrl, no Shift, no Cmd, not Paste)` |
| Insert a newline without submitting. | `Ctrl + Enter`<br />`Cmd + Enter`<br />`Paste + Enter`<br />`Shift + Enter`<br />`Ctrl + J` |
#### External Tools
| Action | Keys |
| ---------------------------------------------- | ---------- |
| Open the current prompt in an external editor. | `Ctrl + X` |
| Paste from the clipboard. | `Ctrl + V` |
#### App Controls
| Action | Keys |
| ------------------------------------------------------------------------------------------------ | ---------------- |
| Toggle detailed error information. | `F12` |
| Toggle the full TODO list. | `Ctrl + T` |
| Show IDE context details. | `Ctrl + G` |
| Toggle Markdown rendering. | `Cmd + M` |
| Toggle copy mode when in alternate buffer mode. | `Ctrl + S` |
| Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl + Y` |
| Toggle Auto Edit (auto-accept edits) mode. | `Shift + Tab` |
| Expand a height-constrained response to show additional lines when not in alternate buffer mode. | `Ctrl + S` |
| Focus the shell input from the gemini input. | `Tab (no Shift)` |
| Focus the Gemini input from the shell input. | `Tab` |
| Clear the terminal screen and redraw the UI. | `Ctrl + L` |
| Restart the application. | `R` |
| Action | Keys |
| ----------------------------------------------------------------- | ---------- |
| Toggle detailed error information. | `F12` |
| Toggle the full TODO list. | `Ctrl + T` |
| Toggle IDE context details. | `Ctrl + G` |
| Toggle Markdown rendering. | `Cmd + M` |
| Toggle copy mode when the terminal is using the alternate buffer. | `Ctrl + S` |
| Expand a height-constrained response to show additional lines. | `Ctrl + S` |
| Toggle focus between the shell and Gemini input. | `Ctrl + F` |
#### Session Control
| Action | Keys |
| -------------------------------------------- | ---------- |
| Cancel the current request or quit the CLI. | `Ctrl + C` |
| Exit the CLI when the input buffer is empty. | `Ctrl + D` |
<!-- KEYBINDINGS-AUTOGEN:END -->
## Additional context-specific shortcuts
- `Option+B/F/M` (macOS only): Are interpreted as `Cmd+B/F/M` even if your
terminal isn't configured to send Meta with Option.
- `Ctrl+Y`: Toggle YOLO (auto-approval) mode for tool calls.
- `Shift+Tab`: Toggle Auto Edit (auto-accept edits) mode.
- `Option+M` (macOS): Entering `µ` with Option+M also toggles Markdown
rendering, matching `Cmd+M`.
- `!` on an empty prompt: Enter or exit shell mode.
- `\` (at end of a line) + `Enter`: Insert a newline without leaving single-line
mode.
- `Esc` pressed twice quickly: Browse and rewind previous interactions.
- `Ctrl+Delete` / `Meta+Delete`: Delete the word to the right of the cursor.
- `Ctrl+B` or `Left Arrow`: Move the cursor one character to the left while
editing text.
- `Ctrl+F` or `Right Arrow`: Move the cursor one character to the right; with an
embedded shell attached, `Ctrl+F` still toggles focus.
- `Ctrl+D` or `Delete`: Remove the character immediately to the right of the
cursor.
- `Ctrl+H` or `Backspace`: Remove the character immediately to the left of the
cursor.
- `Ctrl+Left Arrow` / `Meta+Left Arrow` / `Meta+B`: Move one word to the left.
- `Ctrl+Right Arrow` / `Meta+Right Arrow` / `Meta+F`: Move one word to the
right.
- `Ctrl+W`: Delete the word to the left of the cursor (in addition to
`Ctrl+Backspace` / `Cmd+Backspace`).
- `Ctrl+Z` / `Ctrl+Shift+Z`: Undo or redo the most recent text edit.
- `Meta+Enter`: Open the current input in an external editor (alias for
`Ctrl+X`).
- `Esc` pressed twice quickly: Clear the current input buffer.
- `Up Arrow` / `Down Arrow`: When the cursor is at the top or bottom of a
single-line input, navigate backward or forward through prompt history.
- `Number keys (1-9, multi-digit)` inside selection dialogs: Jump directly to
+16 -12
View File
@@ -1,4 +1,4 @@
# Model routing
## Model routing
Gemini CLI includes a model routing feature that automatically switches to a
fallback model in case of a model failure. This feature is enabled by default
@@ -6,20 +6,24 @@ and provides resilience when the primary model is unavailable.
## How it works
Model routing is managed by the `ModelAvailabilityService`, which monitors model
health and automatically routes requests to available models based on defined
policies.
Model routing is not based on prompt complexity, but is a fallback mechanism.
Here's how it works:
1. **Model failure:** If the currently selected model fails (e.g., due to quota
or server errors), the CLI will initiate the fallback process.
1. **Model failure:** If the currently selected model fails to respond (for
example, due to a server error or other issue), the CLI will initiate the
fallback process.
2. **User consent:** Depending on the failure and the model's policy, the CLI
may prompt you to switch to a fallback model (by default always prompts
you).
2. **User consent:** The CLI will prompt you to ask if you want to switch to
the fallback model. This is handled by the `fallbackModelHandler`.
3. **Model switch:** If approved, or if the policy allows for silent fallback,
the CLI will use an available fallback model for the current turn or the
remainder of the session.
3. **Fallback activation:** If you consent, the CLI will activate the fallback
mode by calling `config.setFallbackMode(true)`.
4. **Model switch:** On the next request, the CLI will use the
`DEFAULT_GEMINI_FLASH_MODEL` as the fallback model. This is handled by the
`resolveModel` function in
`packages/cli/src/zed-integration/zedIntegration.ts` which checks if
`isInFallbackMode()` is true.
### Model selection precedence
+1 -1
View File
@@ -39,7 +39,7 @@ To enable Gemini 3 Pro and Gemini 3 Flash (if available), enable
You can also use the `--model` flag to specify a particular Gemini model on
startup. For more details, refer to the
[configuration documentation](../get-started/configuration.md).
[configuration documentation](./configuration.md).
Changes to these settings will be applied to all subsequent interactions with
Gemini CLI.
+1 -1
View File
@@ -11,7 +11,7 @@ Before using sandboxing, you need to install and set up the Gemini CLI:
npm install -g @google/gemini-cli
```
To verify the installation:
To verify the installation
```bash
gemini --version
+52 -71
View File
@@ -18,50 +18,45 @@ Note: Workspace settings override user settings.
Here is a list of all the available settings, grouped by category and ordered as
they appear in the UI.
<!-- SETTINGS-AUTOGEN:START -->
### General
| UI Label | Setting | Description | Default |
| ------------------------------- | ---------------------------------- | ------------------------------------------------------------- | ------- |
| Preview Features (e.g., models) | `general.previewFeatures` | Enable preview features (e.g., preview models). | `false` |
| Vim Mode | `general.vimMode` | Enable Vim keybindings | `false` |
| Enable Auto Update | `general.enableAutoUpdate` | Enable automatic updates. | `true` |
| Enable Prompt Completion | `general.enablePromptCompletion` | Enable AI-powered prompt completion suggestions while typing. | `false` |
| Debug Keystroke Logging | `general.debugKeystrokeLogging` | Enable debug logging of keystrokes to the console. | `false` |
| Enable Session Cleanup | `general.sessionRetention.enabled` | Enable automatic session cleanup | `false` |
| UI Label | Setting | Description | Default |
| ------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------- | ----------- |
| Preview Features (e.g., models) | `general.previewFeatures` | Enable preview features (e.g., preview models). | `false` |
| Vim Mode | `general.vimMode` | Enable Vim keybindings. | `false` |
| Disable Auto Update | `general.disableAutoUpdate` | Disable automatic updates. | `false` |
| Enable Prompt Completion | `general.enablePromptCompletion` | Enable AI-powered prompt completion suggestions while typing. | `false` |
| Debug Keystroke Logging | `general.debugKeystrokeLogging` | Enable debug logging of keystrokes to the console. | `false` |
| Session Retention | `general.sessionRetention` | Settings for automatic session cleanup. This feature is disabled by default. | `undefined` |
| Enable Session Cleanup | `general.sessionRetention.enabled` | Enable automatic session cleanup. | `false` |
### Output
| UI Label | Setting | Description | Default |
| ------------- | --------------- | ------------------------------------------------------ | -------- |
| Output Format | `output.format` | The format of the CLI output. Can be `text` or `json`. | `"text"` |
| UI Label | Setting | Description | Default |
| ------------- | --------------- | ------------------------------------------------------ | ------- |
| Output Format | `output.format` | The format of the CLI output. Can be `text` or `json`. | `text` |
### UI
| UI Label | Setting | Description | Default |
| ------------------------------ | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Hide Window Title | `ui.hideWindowTitle` | Hide the window title bar | `false` |
| Show Thoughts in Title | `ui.showStatusInTitle` | Show Gemini CLI model thoughts in the terminal window title during the working phase | `false` |
| Dynamic Window Title | `ui.dynamicWindowTitle` | Update the terminal window title with current status icons (Ready: ◇, Action Required: ✋, Working: ✦) | `true` |
| Show Home Directory Warning | `ui.showHomeDirectoryWarning` | Show a warning when running Gemini CLI in the home directory. | `true` |
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` |
| Hide Banner | `ui.hideBanner` | Hide the application banner | `false` |
| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` |
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory path in the footer. | `false` |
| Hide Sandbox Status | `ui.footer.hideSandboxStatus` | Hide the sandbox status indicator in the footer. | `false` |
| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` |
| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window remaining percentage. | `true` |
| Hide Footer | `ui.hideFooter` | Hide the footer from the UI | `false` |
| Show Memory Usage | `ui.showMemoryUsage` | Display memory usage information in the UI | `false` |
| Show Line Numbers | `ui.showLineNumbers` | Show line numbers in the chat. | `true` |
| Show Citations | `ui.showCitations` | Show citations for generated text in the chat. | `false` |
| Show Model Info In Chat | `ui.showModelInfoInChat` | Show the model name in the chat for each model turn. | `false` |
| Use Full Width | `ui.useFullWidth` | Use the entire width of the terminal for output. | `true` |
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` |
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
| Enable Loading Phrases | `ui.accessibility.enableLoadingPhrases` | Enable loading phrases during operations. | `true` |
| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` |
| UI Label | Setting | Description | Default |
| ------------------------------ | ---------------------------------------- | -------------------------------------------------------------------- | ------- |
| Hide Window Title | `ui.hideWindowTitle` | Hide the window title bar. | `false` |
| Show Status in Title | `ui.showStatusInTitle` | Show Gemini CLI status and thoughts in the terminal window title. | `false` |
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI. | `false` |
| Hide Banner | `ui.hideBanner` | Hide the application banner. | `false` |
| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` |
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory path in the footer. | `false` |
| Hide Sandbox Status | `ui.footer.hideSandboxStatus` | Hide the sandbox status indicator in the footer. | `false` |
| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` |
| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window remaining percentage. | `true` |
| Hide Footer | `ui.hideFooter` | Hide the footer from the UI. | `false` |
| Show Memory Usage | `ui.showMemoryUsage` | Display memory usage information in the UI. | `false` |
| Show Line Numbers | `ui.showLineNumbers` | Show line numbers in the chat. | `false` |
| Show Citations | `ui.showCitations` | Show citations for generated text in the chat. | `false` |
| Use Full Width | `ui.useFullWidth` | Use the entire width of the terminal for output. | `true` |
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `true` |
| Disable Loading Phrases | `ui.accessibility.disableLoadingPhrases` | Disable loading phrases for accessibility. | `false` |
| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible. | `false` |
### IDE
@@ -74,7 +69,7 @@ they appear in the UI.
| UI Label | Setting | Description | Default |
| ----------------------- | ---------------------------- | -------------------------------------------------------------------------------------- | ------- |
| Max Session Turns | `model.maxSessionTurns` | Maximum number of user/model/tool turns to keep in a session. -1 means unlimited. | `-1` |
| Compression Threshold | `model.compressionThreshold` | The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3). | `0.5` |
| Compression Threshold | `model.compressionThreshold` | The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3). | `0.2` |
| Skip Next Speaker Check | `model.skipNextSpeakerCheck` | Skip the next speaker check. | `true` |
### Context
@@ -86,46 +81,32 @@ they appear in the UI.
| Respect .gitignore | `context.fileFiltering.respectGitIgnore` | Respect .gitignore files when searching. | `true` |
| Respect .geminiignore | `context.fileFiltering.respectGeminiIgnore` | Respect .geminiignore files when searching. | `true` |
| Enable Recursive File Search | `context.fileFiltering.enableRecursiveFileSearch` | Enable recursive file search functionality when completing @ references in the prompt. | `true` |
| Enable Fuzzy Search | `context.fileFiltering.enableFuzzySearch` | Enable fuzzy search when searching for files. | `true` |
| Disable Fuzzy Search | `context.fileFiltering.disableFuzzySearch` | Disable fuzzy search when searching for files. | `false` |
### Tools
| UI Label | Setting | Description | Default |
| -------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` |
| Auto Accept | `tools.autoAccept` | Automatically accept and execute tool calls that are considered safe (e.g., read-only operations). | `false` |
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
| Enable Tool Output Truncation | `tools.enableToolOutputTruncation` | Enable truncation of large tool outputs. | `true` |
| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Truncate tool output if it is larger than this many characters. Set to -1 to disable. | `4000000` |
| Tool Output Truncation Lines | `tools.truncateToolOutputLines` | The number of lines to keep when truncating tool output. | `1000` |
| Disable LLM Correction | `tools.disableLLMCorrection` | Disable LLM-based error correction for edit tools. When enabled, tools will fail immediately if exact string matches are not found, instead of attempting to self-correct. | `false` |
| UI Label | Setting | Description | Default |
| -------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------- | ------- |
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` |
| Auto Accept | `tools.autoAccept` | Automatically accept and execute tool calls that are considered safe (e.g., read-only operations). | `false` |
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
| Enable Tool Output Truncation | `tools.enableToolOutputTruncation` | Enable truncation of large tool outputs. | `true` |
| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Truncate tool output if it is larger than this many characters. Set to -1 to disable. | `10000` |
| Tool Output Truncation Lines | `tools.truncateToolOutputLines` | The number of lines to keep when truncating tool output. | `100` |
| Enable Message Bus Integration | `tools.enableMessageBusIntegration` | Enable policy-based tool confirmation via message bus integration. | `true` |
### Security
| UI Label | Setting | Description | Default |
| ------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------- | ------- |
| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` |
| Allow Permanent Tool Approval | `security.enablePermanentToolApproval` | Enable the "Allow for all future sessions" option in tool confirmation dialogs. | `false` |
| Blocks extensions from Git | `security.blockGitExtensions` | Blocks installing and loading extensions from Git. | `false` |
| Folder Trust | `security.folderTrust.enabled` | Setting to track whether Folder trust is enabled. | `false` |
| Enable Environment Variable Redaction | `security.environmentVariableRedaction.enabled` | Enable redaction of environment variables that may contain secrets. | `false` |
| UI Label | Setting | Description | Default |
| -------------------------- | ------------------------------ | -------------------------------------------------- | ------- |
| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` |
| Blocks extensions from Git | `security.blockGitExtensions` | Blocks installing and loading extensions from Git. | `false` |
| Folder Trust | `security.folderTrust.enabled` | Setting to track whether Folder trust is enabled. | `false` |
### Experimental
| UI Label | Setting | Description | Default |
| ----------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------- |
| Agent Skills | `experimental.skills` | Enable Agent Skills (experimental). | `false` |
| Enable Codebase Investigator | `experimental.codebaseInvestigatorSettings.enabled` | Enable the Codebase Investigator agent. | `true` |
| Codebase Investigator Max Num Turns | `experimental.codebaseInvestigatorSettings.maxNumTurns` | Maximum number of turns for the Codebase Investigator agent. | `10` |
| Use OSC 52 Paste | `experimental.useOSC52Paste` | Use OSC 52 sequence for pasting instead of clipboardy (useful for remote sessions). | `false` |
| Enable CLI Help Agent | `experimental.cliHelpAgentSettings.enabled` | Enable the CLI Help Agent. | `true` |
| Plan | `experimental.plan` | Enable planning features (Plan Mode and tools). | `false` |
### Hooks
| UI Label | Setting | Description | Default |
| ------------------ | --------------------- | ------------------------------------------------ | ------- |
| Hook Notifications | `hooks.notifications` | Show visual indicators when hooks are executing. | `true` |
<!-- SETTINGS-AUTOGEN:END -->
| UI Label | Setting | Description | Default |
| ----------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------ | ------- |
| Enable Codebase Investigator | `experimental.codebaseInvestigatorSettings.enabled` | Enable the Codebase Investigator agent. | `true` |
| Codebase Investigator Max Num Turns | `experimental.codebaseInvestigatorSettings.maxNumTurns` | Maximum number of turns for the Codebase Investigator agent. | `10` |
-188
View File
@@ -1,188 +0,0 @@
# Agent Skills
_Note: This is an experimental feature enabled via `experimental.skills`. You
can also search for "Skills" within the `/settings` interactive UI to toggle
this and manage other skill-related settings._
Agent Skills allow you to extend Gemini CLI with specialized expertise,
procedural workflows, and task-specific resources. Based on the
[Agent Skills](https://agentskills.io) open standard, a "skill" is a
self-contained directory that packages instructions and assets into a
discoverable capability.
## Overview
Unlike general context files ([`GEMINI.md`](./gemini-md.md)), which provide
persistent workspace-wide background, Skills represent **on-demand expertise**.
This allows Gemini to maintain a vast library of specialized capabilities—such
as security auditing, cloud deployments, or codebase migrations—without
cluttering the model's immediate context window.
Gemini autonomously decides when to employ a skill based on your request and the
skill's description. When a relevant skill is identified, the model "pulls in"
the full instructions and resources required to complete the task using the
`activate_skill` tool.
## Key Benefits
- **Shared Expertise:** Package complex workflows (like a specific team's PR
review process) into a folder that anyone can use.
- **Repeatable Workflows:** Ensure complex multi-step tasks are performed
consistently by providing a procedural framework.
- **Resource Bundling:** Include scripts, templates, or example data alongside
instructions so the agent has everything it needs.
- **Progressive Disclosure:** Only skill metadata (name and description) is
loaded initially. Detailed instructions and resources are only disclosed when
the model explicitly activates the skill, saving context tokens.
## Skill Discovery Tiers
Gemini CLI discovers skills from three primary locations:
1. **Workspace Skills** (`.gemini/skills/`): Workspace-specific skills that are
typically committed to version control and shared with the team.
2. **User Skills** (`~/.gemini/skills/`): Personal skills available across all
your workspaces.
3. **Extension Skills**: Skills bundled within installed
[extensions](../extensions/index.md).
**Precedence:** If multiple skills share the same name, higher-precedence
locations override lower ones: **Workspace > User > Extension**.
## Managing Skills
### In an Interactive Session
Use the `/skills` slash command to view and manage available expertise:
- `/skills list` (default): Shows all discovered skills and their status.
- `/skills disable <name>`: Prevents a specific skill from being used.
- `/skills enable <name>`: Re-enables a disabled skill.
- `/skills reload`: Refreshes the list of discovered skills from all tiers.
_Note: `/skills disable` and `/skills enable` default to the `user` scope. Use
`--scope workspace` to manage workspace-specific settings._
### From the Terminal
The `gemini skills` command provides management utilities:
```bash
# List all discovered skills
gemini skills list
# Install a skill from a Git repository, local directory, or zipped skill file (.skill)
# Uses the user scope by default (~/.gemini/skills)
gemini skills install https://github.com/user/repo.git
gemini skills install /path/to/local/skill
gemini skills install /path/to/local/my-expertise.skill
# Install a specific skill from a monorepo or subdirectory using --path
gemini skills install https://github.com/my-org/my-skills.git --path skills/frontend-design
# Install to the workspace scope (.gemini/skills)
gemini skills install /path/to/skill --scope workspace
# Uninstall a skill by name
gemini skills uninstall my-expertise --scope workspace
# Enable a skill (globally)
gemini skills enable my-expertise
# Disable a skill. Can use --scope to specify workspace or user (defaults to workspace)
gemini skills disable my-expertise --scope workspace
```
## Creating a Skill
A skill is a directory containing a `SKILL.md` file at its root. This file uses
YAML frontmatter for metadata and Markdown for instructions.
### Folder Structure
Skills are self-contained directories. At a minimum, a skill requires a
`SKILL.md` file, but can include other resources:
```text
my-skill/
├── SKILL.md (Required) Instructions and metadata
├── scripts/ (Optional) Executable scripts/tools
├── references/ (Optional) Static documentation and examples
└── assets/ (Optional) Templates and binary resources
```
### Basic Structure (SKILL.md)
```markdown
---
name: <unique-name>
description: <what the skill does and when Gemini should use it>
---
<your instructions for how the agent should behave / use the skill>
```
- **`name`**: A unique identifier (lowercase, alphanumeric, and dashes).
- **`description`**: The most critical field. Gemini uses this to decide when
the skill is relevant. Be specific about the expertise provided.
- **Body**: Everything below the second `---` is injected as expert procedural
guidance for the model.
### Example: Team Code Reviewer
Create `~/.gemini/skills/code-reviewer/SKILL.md`:
```markdown
---
name: code-reviewer
description:
Expertise in reviewing code for style, security, and performance. Use when the
user asks for "feedback," a "review," or to "check" their changes.
---
# Code Reviewer
You are an expert code reviewer. When reviewing code, follow this workflow:
1. **Analyze**: Review the staged changes or specific files provided. Ensure
that the changes are scoped properly and represent minimal changes required
to address the issue.
2. **Style**: Ensure code follows the workspace's conventions and idiomatic
patterns as described in the `GEMINI.md` file.
3. **Security**: Flag any potential security vulnerabilities.
4. **Tests**: Verify that new logic has corresponding test coverage and that
the test coverage adequately validates the changes.
Provide your feedback as a concise bulleted list of "Strengths" and
"Opportunities."
```
### Resource Conventions
While you can structure your skill directory however you like, the Agent Skills
standard encourages these conventions:
- **`scripts/`**: Executable scripts (bash, python, node) the agent can run.
- **`references/`**: Static documentation, schemas, or example data for the
agent to consult.
- **`assets/`**: Code templates, boilerplate, or binary resources.
When a skill is activated, Gemini CLI provides the model with a tree view of the
entire skill directory, allowing it to discover and utilize these assets.
## How it Works (Security & Privacy)
1. **Discovery**: At the start of a session, Gemini CLI scans the discovery
tiers and injects the name and description of all enabled skills into the
system prompt.
2. **Activation**: When Gemini identifies a task matching a skill's
description, it calls the `activate_skill` tool.
3. **Consent**: You will see a confirmation prompt in the UI detailing the
skill's name, purpose, and the directory path it will gain access to.
4. **Injection**: Upon your approval:
- The `SKILL.md` body and folder structure is added to the conversation
history.
- The skill's directory is added to the agent's allowed file paths, granting
it permission to read any bundled assets.
5. **Execution**: The model proceeds with the specialized expertise active. It
is instructed to prioritize the skill's procedural guidance within reason.
-93
View File
@@ -1,93 +0,0 @@
# System Prompt Override (GEMINI_SYSTEM_MD)
The core system instructions that guide Gemini CLI can be completely replaced
with your own Markdown file. This feature is controlled via the
`GEMINI_SYSTEM_MD` environment variable.
## Overview
The `GEMINI_SYSTEM_MD` variable instructs the CLI to use an external Markdown
file for its system prompt, completely overriding the built-in default. This is
a full replacement, not a merge. If you use a custom file, none of the original
core instructions will apply unless you include them yourself.
This feature is intended for advanced users who need to enforce strict,
project-specific behavior or create a customized persona.
> Tip: You can export the current default system prompt to a file first, review
> it, and then selectively modify or replace it (see
> [“Export the default prompt”](#export-the-default-prompt-recommended)).
## How to enable
You can set the environment variable temporarily in your shell, or persist it
via a `.gemini/.env` file. See
[Persisting Environment Variables](../get-started/authentication.md#persisting-environment-variables).
- Use the project default path (`.gemini/system.md`):
- `GEMINI_SYSTEM_MD=true` or `GEMINI_SYSTEM_MD=1`
- The CLI reads `./.gemini/system.md` (relative to your current project
directory).
- Use a custom file path:
- `GEMINI_SYSTEM_MD=/absolute/path/to/my-system.md`
- Relative paths are supported and resolved from the current working
directory.
- Tilde expansion is supported (e.g., `~/my-system.md`).
- Disable the override (use builtin prompt):
- `GEMINI_SYSTEM_MD=false` or `GEMINI_SYSTEM_MD=0` or unset the variable.
If the override is enabled but the target file does not exist, the CLI will
error with: `missing system prompt file '<path>'`.
## Quick examples
- Oneoff session using a project file:
- `GEMINI_SYSTEM_MD=1 gemini`
- Persist for a project using `.gemini/.env`:
- Create `.gemini/system.md`, then add to `.gemini/.env`:
- `GEMINI_SYSTEM_MD=1`
- Use a custom file under your home directory:
- `GEMINI_SYSTEM_MD=~/prompts/SYSTEM.md gemini`
## UI indicator
When `GEMINI_SYSTEM_MD` is active, the CLI shows a `|⌐■_■|` indicator in the UI
to signal custom systemprompt mode.
## Export the default prompt (recommended)
Before overriding, export the current default prompt so you can review required
safety and workflow rules.
- Write the builtin prompt to the project default path:
- `GEMINI_WRITE_SYSTEM_MD=1 gemini`
- Or write to a custom path:
- `GEMINI_WRITE_SYSTEM_MD=~/prompts/DEFAULT_SYSTEM.md gemini`
This creates the file and writes the current builtin system prompt to it.
## Best practices: SYSTEM.md vs GEMINI.md
- SYSTEM.md (firmware):
- Nonnegotiable operational rules: safety, tooluse protocols, approvals, and
mechanics that keep the CLI reliable.
- Stable across tasks and projects (or per project when needed).
- GEMINI.md (strategy):
- Persona, goals, methodologies, and project/domain context.
- Evolves per task; relies on SYSTEM.md for safe execution.
Keep SYSTEM.md minimal but complete for safety and tool operation. Keep
GEMINI.md focused on highlevel guidance and project specifics.
## Troubleshooting
- Error: `missing system prompt file '…'`
- Ensure the referenced path exists and is readable.
- For `GEMINI_SYSTEM_MD=1|true`, create `./.gemini/system.md` in your project.
- Override not taking effect
- Confirm the variable is loaded (use `.gemini/.env` or export in your shell).
- Paths are resolved from the current working directory; try an absolute path.
- Restore defaults
- Unset `GEMINI_SYSTEM_MD` or set it to `0`/`false`.
+3 -23
View File
@@ -8,10 +8,8 @@ Learn how to enable and setup OpenTelemetry for Gemini CLI.
- [Configuration](#configuration)
- [Google Cloud telemetry](#google-cloud-telemetry)
- [Prerequisites](#prerequisites)
- [Authenticating with CLI Credentials](#authenticating-with-cli-credentials)
- [Direct export (recommended)](#direct-export-recommended)
- [Collector-based export (advanced)](#collector-based-export-advanced)
- [Monitoring Dashboards](#monitoring-dashboards)
- [Local telemetry](#local-telemetry)
- [File-based output (recommended)](#file-based-output-recommended)
- [Collector-based export (advanced)](#collector-based-export-advanced-1)
@@ -215,24 +213,6 @@ forward data to Google Cloud.
- Open `~/.gemini/tmp/<projectHash>/otel/collector-gcp.log` to view local
collector logs.
### Monitoring Dashboards
Gemini CLI provides a pre-configured
[Google Cloud Monitoring](https://cloud.google.com/monitoring) dashboard to
visualize your telemetry.
This dashboard can be found under **Google Cloud Monitoring Dashboard
Templates** as "**Gemini CLI Monitoring**".
![Gemini CLI Monitoring Dashboard Overview](/docs/assets/monitoring-dashboard-overview.png)
![Gemini CLI Monitoring Dashboard Metrics](/docs/assets/monitoring-dashboard-metrics.png)
![Gemini CLI Monitoring Dashboard Logs](/docs/assets/monitoring-dashboard-logs.png)
To learn more, check out this blog post:
[Instant insights: Gemini CLIs new pre-configured monitoring dashboards](https://cloud.google.com/blog/topics/developers-practitioners/instant-insights-gemini-clis-new-pre-configured-monitoring-dashboards/).
## Local telemetry
For local development and debugging, you can capture telemetry data locally:
@@ -317,7 +297,7 @@ Captures startup configuration and user prompt submissions.
#### Tools
Captures tool executions, output truncation, and Edit behavior.
Captures tool executions, output truncation, and Smart Edit behavior.
- `gemini_cli.tool_call`: Emitted for each tool (function) call.
- **Attributes**:
@@ -345,11 +325,11 @@ Captures tool executions, output truncation, and Edit behavior.
- `lines` (int)
- `prompt_id` (string)
- `gemini_cli.edit_strategy`: Edit strategy chosen.
- `gemini_cli.smart_edit_strategy`: Smart Edit strategy chosen.
- **Attributes**:
- `strategy` (string)
- `gemini_cli.edit_correction`: Edit correction result.
- `gemini_cli.smart_edit_correction`: Smart Edit correction result.
- **Attributes**:
- `correction` ("success" | "failure")
+2
View File
@@ -86,6 +86,7 @@ color keys. For example:
- `Gray`
- `DiffAdded` (optional, for added lines in diffs)
- `DiffRemoved` (optional, for removed lines in diffs)
- `DiffModified` (optional, for modified lines in diffs)
You can also override individual UI text roles by adding a nested `text` object.
This object supports the keys `primary`, `secondary`, `link`, `accent`, and
@@ -156,6 +157,7 @@ custom theme defined in `settings.json`.
"Gray": "#ABB2BF",
"DiffAdded": "#A6E3A1",
"DiffRemoved": "#F38BA8",
"DiffModified": "#89B4FA",
"GradientColors": ["#4796E4", "#847ACE", "#C3677F"]
}
```
-4
View File
@@ -2,10 +2,6 @@
This page contains tutorials for interacting with Gemini CLI.
## Agent Skills
- [Getting Started with Agent Skills](./tutorials/skills-getting-started.md)
## Setting up a Model Context Protocol (MCP) server
> [!CAUTION] Before using a third-party MCP server, ensure you trust its source
@@ -1,124 +0,0 @@
# Getting Started with Agent Skills
Agent Skills allow you to extend Gemini CLI with specialized expertise. This
tutorial will guide you through creating your first skill, enabling it, and
using it in a session.
## 1. Enable Agent Skills
Agent Skills are currently an experimental feature and must be enabled in your
settings.
### Via the interactive UI
1. Start a Gemini CLI session by running `gemini`.
2. Type `/settings` to open the interactive settings dialog.
3. Search for "Skills".
4. Toggle **Agent Skills** to `true`.
5. Press `Esc` to save and exit. You may need to restart the CLI for the
changes to take effect.
### Via `settings.json`
Alternatively, you can manually edit your global settings file at
`~/.gemini/settings.json` (create it if it doesn't exist):
```json
{
"experimental": {
"skills": true
}
}
```
## 2. Create Your First Skill
A skill is a directory containing a `SKILL.md` file. Let's create an **API
Auditor** skill that helps you verify if local or remote endpoints are
responding correctly.
1. **Create the skill directory structure:**
```bash
mkdir -p .gemini/skills/api-auditor/scripts
```
2. **Create the `SKILL.md` file:** Create a file at
`.gemini/skills/api-auditor/SKILL.md` with the following content:
```markdown
---
name: api-auditor
description:
Expertise in auditing and testing API endpoints. Use when the user asks to
"check", "test", or "audit" a URL or API.
---
# API Auditor Instructions
You act as a QA engineer specialized in API reliability. When this skill is
active, you MUST:
1. **Audit**: Use the bundled `scripts/audit.js` utility to check the
status of the provided URL.
2. **Report**: Analyze the output (status codes, latency) and explain any
failures in plain English.
3. **Secure**: Remind the user if they are testing a sensitive endpoint
without an `https://` protocol.
```
3. **Create the bundled Node.js script:** Create a file at
`.gemini/skills/api-auditor/scripts/audit.js`. This script will be used by
the agent to perform the actual check:
```javascript
// .gemini/skills/api-auditor/scripts/audit.js
const url = process.argv[2];
if (!url) {
console.error('Usage: node audit.js <url>');
process.exit(1);
}
console.log(`Auditing ${url}...`);
fetch(url, { method: 'HEAD' })
.then((r) => console.log(`Result: Success (Status ${r.status})`))
.catch((e) => console.error(`Result: Failed (${e.message})`));
```
## 3. Verify the Skill is Discovered
Use the `/skills` slash command (or `gemini skills list` from your terminal) to
see if Gemini CLI has found your new skill.
In a Gemini CLI session:
```
/skills list
```
You should see `api-auditor` in the list of available skills.
## 4. Use the Skill in a Chat
Now, let's see the skill in action. Start a new session and ask a question about
an endpoint.
**User:** "Can you audit http://geminili.com"
Gemini will recognize the request matches the `api-auditor` description and will
ask for your permission to activate it.
**Model:** (After calling `activate_skill`) "I've activated the **api-auditor**
skill. I'll run the audit script now..."
Gemini will then use the `run_shell_command` tool to execute your bundled Node
script:
`node .gemini/skills/api-auditor/scripts/audit.js http://geminili.com`
## Next Steps
- Explore [Agent Skills Authoring Guide](../skills.md#creating-a-skill) to learn
about more advanced skill features.
- Learn how to share skills via [Extensions](../../extensions/index.md).
-2
View File
@@ -83,9 +83,7 @@ The processor automatically detects and prevents circular imports:
# file-a.md
@./file-b.md
```
```markdown
# file-b.md
@./file-a.md <!-- This will be detected and prevented -->
+2 -3
View File
@@ -258,9 +258,8 @@ The Gemini CLI ships with a set of default policies to provide a safe
out-of-the-box experience.
- **Read-only tools** (like `read_file`, `glob`) are generally **allowed**.
- **Agent delegation** (like `delegate_to_agent`) defaults to **`ask_user`** to
ensure remote agents can prompt for confirmation, but local sub-agent actions
are executed silently and checked individually.
- **Agent delegation** (like `delegate_to_agent`) is **allowed** (sub-agent
actions are checked individually).
- **Write tools** (like `write_file`, `run_shell_command`) default to
**`ask_user`**.
- In **`yolo`** mode, a high-priority rule allows all tools.
+2 -39
View File
@@ -222,45 +222,9 @@ need this for extensions built to expose commands and prompts.
Restart the CLI again. The model will now have the context from your `GEMINI.md`
file in every session where the extension is active.
## (Optional) Step 6: Add an Agent Skill
## Step 6: Releasing your extension
_Note: This is an experimental feature enabled via `experimental.skills`._
[Agent Skills](../cli/skills.md) let you bundle specialized expertise and
procedural workflows. Unlike `GEMINI.md`, which provides persistent context,
skills are activated only when needed, saving context tokens.
1. Create a `skills` directory and a subdirectory for your skill:
```bash
mkdir -p skills/security-audit
```
2. Create a `skills/security-audit/SKILL.md` file:
```markdown
---
name: security-audit
description:
Expertise in auditing code for security vulnerabilities. Use when the user
asks to "check for security issues" or "audit" their changes.
---
# Security Auditor
You are an expert security researcher. When auditing code:
1. Look for common vulnerabilities (OWASP Top 10).
2. Check for hardcoded secrets or API keys.
3. Suggest remediation steps for any findings.
```
Skills bundled with your extension are automatically discovered and can be
activated by the model during a session when it identifies a relevant task.
## Step 7: Release your extension
Once you're happy with your extension, you can share it with others. The two
Once you are happy with your extension, you can share it with others. The two
primary ways of releasing extensions are via a Git repository or through GitHub
Releases. Using a public Git repository is the simplest method.
@@ -275,7 +239,6 @@ You've successfully created a Gemini CLI extension! You learned how to:
- Add custom tools with an MCP server.
- Create convenient custom commands.
- Provide persistent context to the model.
- Bundle specialized Agent Skills.
- Link your extension for local development.
From here, you can explore more advanced features and build powerful new
+11 -95
View File
@@ -2,10 +2,10 @@
_This documentation is up-to-date with the v0.4.0 release._
Gemini CLI extensions package prompts, MCP servers, Agent Skills, and custom
commands into a familiar and user-friendly format. With extensions, you can
expand the capabilities of Gemini CLI and share those capabilities with others.
They're designed to be easily installable and shareable.
Gemini CLI extensions package prompts, MCP servers, and custom commands into a
familiar and user-friendly format. With extensions, you can expand the
capabilities of Gemini CLI and share those capabilities with others. They are
designed to be easily installable and shareable.
To see examples of extensions, you can browse a gallery of
[Gemini CLI extensions](https://geminicli.com/extensions/browse/).
@@ -165,7 +165,7 @@ The file has the following structure:
- `version`: The version of the extension.
- `mcpServers`: A map of MCP servers to settings. The key is the name of the
server, and the value is the server configuration. These servers will be
loaded on startup just like MCP servers settings in a
loaded on startup just like MCP servers settingsd in a
[`settings.json` file](../get-started/configuration.md). If both an extension
and a `settings.json` file settings an MCP server with the same name, the
server defined in the `settings.json` file takes precedence.
@@ -226,13 +226,13 @@ key. The value will be saved to a `.env` file in the extension's directory
You can view a list of an extension's settings by running:
```
gemini extensions list
gemini extensions settings list <extension name>
```
and you can update a given setting using:
```
gemini extensions config <extension name> [setting name] [--scope <scope>]
gemini extensions settings set <extension name> <setting name> [--scope <scope>]
```
- `--scope`: The scope to set the setting in (`user` or `workspace`). This is
@@ -263,88 +263,6 @@ Would provide these commands:
- `/deploy` - Shows as `[gcp] Custom command from deploy.toml` in help
- `/gcs:sync` - Shows as `[gcp] Custom command from sync.toml` in help
### Agent Skills
_Note: This is an experimental feature enabled via `experimental.skills`._
Extensions can bundle [Agent Skills](../cli/skills.md) to provide on-demand
expertise and specialized workflows. To include skills in your extension, place
them in a `skills/` subdirectory within the extension directory. Each skill must
follow the [Agent Skills structure](../cli/skills.md#folder-structure),
including a `SKILL.md` file.
**Example**
An extension named `security-toolkit` with the following structure:
```
.gemini/extensions/security-toolkit/
├── gemini-extension.json
└── skills/
├── audit/
│ ├── SKILL.md
│ └── scripts/
│ └── scan.py
└── hardening/
└── SKILL.md
```
Upon installation, these skills will be discovered by Gemini CLI and can be
activated during a session when the model identifies a task matching their
descriptions.
Extension skills have the lowest precedence and will be overridden by user or
workspace skills of the same name. They can be viewed and managed (enabled or
disabled) using the [`/skills` command](../cli/skills.md#managing-skills).
### Hooks
Extensions can provide [hooks](../hooks/index.md) to intercept and customize
Gemini CLI behavior at specific lifecycle events. Hooks provided by an extension
must be defined in a `hooks/hooks.json` file within the extension directory.
> [!IMPORTANT] Hooks are not defined directly in `gemini-extension.json`. The
> CLI specifically looks for the `hooks/hooks.json` file.
#### Directory structure
```
.gemini/extensions/my-extension/
├── gemini-extension.json
└── hooks/
└── hooks.json
```
#### `hooks/hooks.json` format
The `hooks.json` file contains a `hooks` object where keys are
[event names](../hooks/reference.md#supported-events) and values are arrays of
[hook definitions](../hooks/reference.md#hook-definition).
```json
{
"hooks": {
"before_agent": [
{
"hooks": [
{
"type": "command",
"command": "node ${extensionPath}/scripts/setup.js",
"name": "Extension Setup"
}
]
}
]
}
}
```
#### Supported variables
Just like `gemini-extension.json`, the `hooks/hooks.json` file supports
[variable substitution](#variables). This is particularly useful for referencing
scripts within the extension directory using `${extensionPath}`.
### Conflict resolution
Extension commands have the lowest precedence. When a conflict occurs with user
@@ -360,12 +278,11 @@ For example, if both a user and the `gcp` extension define a `deploy` command:
- `/gcp.deploy` - Executes the extension's deploy command (marked with `[gcp]`
tag)
### Variables
## Variables
Gemini CLI extensions allow variable substitution in both
`gemini-extension.json` and `hooks/hooks.json`. This can be useful if e.g., you
need the current directory to run an MCP server or hook script using
`"cwd": "${extensionPath}${/}run.ts"`.
Gemini CLI extensions allow variable substitution in `gemini-extension.json`.
This can be useful if e.g., you need the current directory to run an MCP server
using `"cwd": "${extensionPath}${/}run.ts"`.
**Supported variables:**
@@ -374,4 +291,3 @@ need the current directory to run an MCP server or hook script using
| `${extensionPath}` | The fully-qualified path of the extension in the user's filesystem e.g., '/Users/username/.gemini/extensions/example-extension'. This will not unwrap symlinks. |
| `${workspacePath}` | The fully-qualified path of the current workspace. |
| `${/} or ${pathSeparator}` | The path separator (differs per OS). |
| `${process.execPath}` | The path to the Node.js binary executing the CLI. |
+43 -198
View File
@@ -110,13 +110,13 @@ their corresponding top-level category object in your `settings.json` file.
- **Description:** Enable Vim keybindings
- **Default:** `false`
- **`general.enableAutoUpdate`** (boolean):
- **Description:** Enable automatic updates.
- **Default:** `true`
- **`general.disableAutoUpdate`** (boolean):
- **Description:** Disable automatic updates
- **Default:** `false`
- **`general.enableAutoUpdateNotification`** (boolean):
- **Description:** Enable update notification prompts.
- **Default:** `true`
- **`general.disableUpdateNag`** (boolean):
- **Description:** Disable update notification prompts.
- **Default:** `false`
- **`general.checkpointing.enabled`** (boolean):
- **Description:** Enable session checkpointing for recovery
@@ -159,7 +159,7 @@ their corresponding top-level category object in your `settings.json` file.
#### `output`
- **`output.format`** (enum):
- **Description:** The format of the CLI output. Can be `text` or `json`.
- **Description:** The format of the CLI output.
- **Default:** `"text"`
- **Values:** `"text"`, `"json"`
@@ -180,21 +180,10 @@ their corresponding top-level category object in your `settings.json` file.
- **Requires restart:** Yes
- **`ui.showStatusInTitle`** (boolean):
- **Description:** Show Gemini CLI model thoughts in the terminal window title
during the working phase
- **Description:** Show Gemini CLI status and thoughts in the terminal window
title
- **Default:** `false`
- **`ui.dynamicWindowTitle`** (boolean):
- **Description:** Update the terminal window title with current status icons
(Ready: ◇, Action Required: ✋, Working: ✦)
- **Default:** `true`
- **`ui.showHomeDirectoryWarning`** (boolean):
- **Description:** Show a warning when running Gemini CLI in the home
directory.
- **Default:** `true`
- **Requires restart:** Yes
- **`ui.hideTips`** (boolean):
- **Description:** Hide helpful tips in the UI
- **Default:** `false`
@@ -266,9 +255,9 @@ their corresponding top-level category object in your `settings.json` file.
provided, the CLI cycles through these instead of the defaults.
- **Default:** `[]`
- **`ui.accessibility.enableLoadingPhrases`** (boolean):
- **Description:** Enable loading phrases during operations.
- **Default:** `true`
- **`ui.accessibility.disableLoadingPhrases`** (boolean):
- **Description:** Disable loading phrases for accessibility
- **Default:** `false`
- **Requires restart:** Yes
- **`ui.accessibility.screenReader`** (boolean):
@@ -280,7 +269,7 @@ their corresponding top-level category object in your `settings.json` file.
#### `ide`
- **`ide.enabled`** (boolean):
- **Description:** Enable IDE integration mode.
- **Description:** Enable IDE integration mode
- **Default:** `false`
- **Requires restart:** Yes
@@ -557,14 +546,6 @@ their corresponding top-level category object in your `settings.json` file.
used.
- **Default:** `[]`
#### `agents`
- **`agents.overrides`** (object):
- **Description:** Override settings for specific agents, e.g. to disable the
agent, set a custom model config, or run config.
- **Default:** `{}`
- **Requires restart:** Yes
#### `context`
- **`context.fileName`** (string | string[]):
@@ -592,12 +573,12 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **`context.fileFiltering.respectGitIgnore`** (boolean):
- **Description:** Respect .gitignore files when searching.
- **Description:** Respect .gitignore files when searching
- **Default:** `true`
- **Requires restart:** Yes
- **`context.fileFiltering.respectGeminiIgnore`** (boolean):
- **Description:** Respect .geminiignore files when searching.
- **Description:** Respect .geminiignore files when searching
- **Default:** `true`
- **Requires restart:** Yes
@@ -607,9 +588,9 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `true`
- **Requires restart:** Yes
- **`context.fileFiltering.enableFuzzySearch`** (boolean):
- **Description:** Enable fuzzy search when searching for files.
- **Default:** `true`
- **`context.fileFiltering.disableFuzzySearch`** (boolean):
- **Description:** Disable fuzzy search when searching for files.
- **Default:** `false`
- **Requires restart:** Yes
#### `tools`
@@ -640,11 +621,6 @@ their corresponding top-level category object in your `settings.json` file.
shell command. Defaults to 5 minutes.
- **Default:** `300`
- **`tools.shell.enableShellOutputEfficiency`** (boolean):
- **Description:** Enable shell output efficiency optimizations for better
performance.
- **Default:** `true`
- **`tools.autoAccept`** (boolean):
- **Description:** Automatically accept and execute tool calls that are
considered safe (e.g., read-only operations).
@@ -703,17 +679,20 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `1000`
- **Requires restart:** Yes
- **`tools.disableLLMCorrection`** (boolean):
- **Description:** Disable LLM-based error correction for edit tools. When
enabled, tools will fail immediately if exact string matches are not found,
instead of attempting to self-correct.
- **Default:** `false`
- **`tools.enableMessageBusIntegration`** (boolean):
- **Description:** Enable policy-based tool confirmation via message bus
integration. When enabled, tools automatically respect policy engine
decisions (ALLOW/DENY/ASK_USER) without requiring individual tool
implementations.
- **Default:** `true`
- **Requires restart:** Yes
- **`tools.enableHooks`** (boolean):
- **Description:** Enables the hooks system experiment. When disabled, the
hooks system is completely deactivated regardless of other settings.
- **Default:** `true`
- **Description:** Enable the hooks system for intercepting and customizing
Gemini CLI behavior. When enabled, hooks configured in settings will execute
at appropriate lifecycle events (BeforeTool, AfterTool, BeforeModel, etc.).
Requires MessageBus integration.
- **Default:** `false`
- **Requires restart:** Yes
#### `mcp`
@@ -733,6 +712,12 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `undefined`
- **Requires restart:** Yes
#### `useSmartEdit`
- **`useSmartEdit`** (boolean):
- **Description:** Enable the smart-edit tool instead of the replace tool.
- **Default:** `true`
#### `useWriteTodos`
- **`useWriteTodos`** (boolean):
@@ -761,22 +746,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **Requires restart:** Yes
- **`security.environmentVariableRedaction.allowed`** (array):
- **Description:** Environment variables to always allow (bypass redaction).
- **Default:** `[]`
- **Requires restart:** Yes
- **`security.environmentVariableRedaction.blocked`** (array):
- **Description:** Environment variables to always redact.
- **Default:** `[]`
- **Requires restart:** Yes
- **`security.environmentVariableRedaction.enabled`** (boolean):
- **Description:** Enable redaction of environment variables that may contain
secrets.
- **Default:** `false`
- **Requires restart:** Yes
- **`security.auth.selectedType`** (string):
- **Description:** The currently selected authentication type.
- **Default:** `undefined`
@@ -820,8 +789,7 @@ their corresponding top-level category object in your `settings.json` file.
#### `experimental`
- **`experimental.enableAgents`** (boolean):
- **Description:** Enable local and remote subagents. Warning: Experimental
feature, uses YOLO mode for subagents
- **Description:** Enable local and remote subagents.
- **Default:** `false`
- **Requires restart:** Yes
@@ -830,28 +798,18 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `true`
- **Requires restart:** Yes
- **`experimental.extensionConfig`** (boolean):
- **Description:** Enable requesting and fetching of extension settings.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.enableEventDrivenScheduler`** (boolean):
- **Description:** Enables event-driven scheduler within the CLI session.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.extensionReloading`** (boolean):
- **Description:** Enables extension loading/unloading within the CLI session.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.jitContext`** (boolean):
- **Description:** Enable Just-In-Time (JIT) context loading.
- **`experimental.isModelAvailabilityServiceEnabled`** (boolean):
- **Description:** Enable model routing using new availability service.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.skills`** (boolean):
- **Description:** Enable Agent Skills (experimental).
- **`experimental.jitContext`** (boolean):
- **Description:** Enable Just-In-Time (JIT) context loading.
- **Default:** `false`
- **Requires restart:** Yes
@@ -882,44 +840,13 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `"auto"`
- **Requires restart:** Yes
- **`experimental.useOSC52Paste`** (boolean):
- **Description:** Use OSC 52 sequence for pasting instead of clipboardy
(useful for remote sessions).
- **Default:** `false`
- **`experimental.cliHelpAgentSettings.enabled`** (boolean):
- **Description:** Enable the CLI Help Agent.
- **Default:** `true`
- **Requires restart:** Yes
- **`experimental.plan`** (boolean):
- **Description:** Enable planning features (Plan Mode and tools).
- **Default:** `false`
- **Requires restart:** Yes
#### `skills`
- **`skills.disabled`** (array):
- **Description:** List of disabled skills.
- **Default:** `[]`
- **Requires restart:** Yes
#### `hooks`
- **`hooks.enabled`** (boolean):
- **Description:** Canonical toggle for the hooks system. When disabled, no
hooks will be executed.
- **Default:** `false`
- **`hooks.disabled`** (array):
- **Description:** List of hook names (commands) that should be disabled.
Hooks in this list will not execute even if configured.
- **Default:** `[]`
- **`hooks.notifications`** (boolean):
- **Description:** Show visual indicators when hooks are executing.
- **Default:** `true`
- **`hooks.BeforeTool`** (array):
- **Description:** Hooks that execute before tool execution. Can intercept,
validate, or modify tool calls.
@@ -974,25 +901,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Description:** Hooks that execute before tool selection. Can filter or
prioritize available tools dynamically.
- **Default:** `[]`
#### `admin`
- **`admin.secureModeEnabled`** (boolean):
- **Description:** If true, disallows yolo mode from being used.
- **Default:** `false`
- **`admin.extensions.enabled`** (boolean):
- **Description:** If false, disallows extensions from being installed or
used.
- **Default:** `true`
- **`admin.mcp.enabled`** (boolean):
- **Description:** If false, disallows MCP servers from being used.
- **Default:** `true`
- **`admin.skills.enabled`** (boolean):
- **Description:** If false, disallows agent skills from being used.
- **Default:** `true`
<!-- SETTINGS-AUTOGEN:END -->
#### `mcpServers`
@@ -1170,7 +1078,7 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
- **`GEMINI_MODEL`**:
- Specifies the default Gemini model to use.
- Overrides the hardcoded default
- Example: `export GEMINI_MODEL="gemini-3-flash-preview"`
- Example: `export GEMINI_MODEL="gemini-2.5-flash"`
- **`GOOGLE_API_KEY`**:
- Your Google Cloud API key.
- Required for using Vertex AI in express mode.
@@ -1225,18 +1133,6 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
- **`GEMINI_SANDBOX`**:
- Alternative to the `sandbox` setting in `settings.json`.
- Accepts `true`, `false`, `docker`, `podman`, or a custom command string.
- **`GEMINI_SYSTEM_MD`**:
- Replaces the builtin system prompt with content from a Markdown file.
- `true`/`1`: Use project default path `./.gemini/system.md`.
- Any other string: Treat as a path (relative/absolute supported, `~`
expands).
- `false`/`0` or unset: Use the builtin prompt. See
[System Prompt Override](../cli/system-prompt.md).
- **`GEMINI_WRITE_SYSTEM_MD`**:
- Writes the current builtin system prompt to a file for review.
- `true`/`1`: Write to `./.gemini/system.md`. Otherwise treat the value as a
path.
- Run the CLI once with this set to generate the file.
- **`SEATBELT_PROFILE`** (macOS specific):
- Switches the Seatbelt (`sandbox-exec`) profile on macOS.
- `permissive-open`: (Default) Restricts writes to the project folder (and a
@@ -1262,52 +1158,6 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
- Specifies the endpoint for the code assist server.
- This is useful for development and testing.
### Environment variable redaction
To prevent accidental leakage of sensitive information, Gemini CLI automatically
redacts potential secrets from environment variables when executing tools (such
as shell commands). This "best effort" redaction applies to variables inherited
from the system or loaded from `.env` files.
**Default Redaction Rules:**
- **By Name:** Variables are redacted if their names contain sensitive terms
like `TOKEN`, `SECRET`, `PASSWORD`, `KEY`, `AUTH`, `CREDENTIAL`, `PRIVATE`, or
`CERT`.
- **By Value:** Variables are redacted if their values match known secret
patterns, such as:
- Private keys (RSA, OpenSSH, PGP, etc.)
- Certificates
- URLs containing credentials
- API keys and tokens (GitHub, Google, AWS, Stripe, Slack, etc.)
- **Specific Blocklist:** Certain variables like `CLIENT_ID`, `DB_URI`,
`DATABASE_URL`, and `CONNECTION_STRING` are always redacted by default.
**Allowlist (Never Redacted):**
- Common system variables (e.g., `PATH`, `HOME`, `USER`, `SHELL`, `TERM`,
`LANG`).
- Variables starting with `GEMINI_CLI_`.
- GitHub Action specific variables.
**Configuration:**
You can customize this behavior in your `settings.json` file:
- **`security.allowedEnvironmentVariables`**: A list of variable names to
_never_ redact, even if they match sensitive patterns.
- **`security.blockedEnvironmentVariables`**: A list of variable names to
_always_ redact, even if they don't match sensitive patterns.
```json
{
"security": {
"allowedEnvironmentVariables": ["MY_PUBLIC_KEY", "NOT_A_SECRET_TOKEN"],
"blockedEnvironmentVariables": ["INTERNAL_IP_ADDRESS"]
}
}
```
## Command-line arguments
Arguments passed directly when running the CLI can override other configurations
@@ -1315,7 +1165,7 @@ for that specific session.
- **`--model <model_name>`** (**`-m <model_name>`**):
- Specifies the Gemini model to use for this session.
- Example: `npm start -- --model gemini-3-pro-preview`
- Example: `npm start -- --model gemini-1.5-pro-latest`
- **`--prompt <your_prompt>`** (**`-p <your_prompt>`**):
- Used to pass a prompt directly to the command. This invokes Gemini CLI in a
non-interactive mode.
@@ -1338,8 +1188,7 @@ for that specific session.
- **`--sandbox`** (**`-s`**):
- Enables sandbox mode for this session.
- **`--debug`** (**`-d`**):
- Enables debug mode for this session, providing more verbose output. Open the
debug console with F12 to see the additional logging.
- Enables debug mode for this session, providing more verbose output.
- **`--help`** (or **`-h`**):
- Displays help information about command-line arguments.
@@ -1351,10 +1200,6 @@ for that specific session.
- `auto_edit`: Automatically approve edit tools (replace, write_file) while
prompting for others
- `yolo`: Automatically approve all tool calls (equivalent to `--yolo`)
- `plan`: Read-only mode for tool calls (requires experimental planning to
be enabled).
> **Note:** This mode is currently under development and not yet fully
> functional.
- Cannot be used together with `--yolo`. Use `--approval-mode=yolo` instead of
`--yolo` for the new unified approach.
- Example: `gemini --approval-mode auto_edit`
+143
View File
@@ -0,0 +1,143 @@
Note: This page will be replaced by [installation.md](installation.md).
# Gemini CLI installation, execution, and deployment
Install and run Gemini CLI. This document provides an overview of Gemini CLI's
installation methods and deployment architecture.
## How to install and/or run Gemini CLI
There are several ways to run Gemini CLI. The recommended option depends on how
you intend to use Gemini CLI.
- As a standard installation. This is the most straightforward method of using
Gemini CLI.
- In a sandbox. This method offers increased security and isolation.
- From the source. This is recommended for contributors to the project.
### 1. Standard installation (recommended for standard users)
This is the recommended way for end-users to install Gemini CLI. It involves
downloading the Gemini CLI package from the NPM registry.
- **Global install:**
```bash
npm install -g @google/gemini-cli
```
Then, run the CLI from anywhere:
```bash
gemini
```
- **NPX execution:**
```bash
# Execute the latest version from NPM without a global install
npx @google/gemini-cli
```
### 2. Run in a sandbox (Docker/Podman)
For security and isolation, Gemini CLI can be run inside a container. This is
the default way that the CLI executes tools that might have side effects.
- **Directly from the registry:** You can run the published sandbox image
directly. This is useful for environments where you only have Docker and want
to run the CLI.
```bash
# Run the published sandbox image
docker run --rm -it us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.1.1
```
- **Using the `--sandbox` flag:** If you have Gemini CLI installed locally
(using the standard installation described above), you can instruct it to run
inside the sandbox container.
```bash
gemini --sandbox -y -p "your prompt here"
```
### 3. Run from source (recommended for Gemini CLI contributors)
Contributors to the project will want to run the CLI directly from the source
code.
- **Development mode:** This method provides hot-reloading and is useful for
active development.
```bash
# From the root of the repository
npm run start
```
- **Production-like mode (Linked package):** This method simulates a global
installation by linking your local package. It's useful for testing a local
build in a production workflow.
```bash
# Link the local cli package to your global node_modules
npm link packages/cli
# Now you can run your local version using the `gemini` command
gemini
```
---
### 4. Running the latest Gemini CLI commit from GitHub
You can run the most recently committed version of Gemini CLI directly from the
GitHub repository. This is useful for testing features still in development.
```bash
# Execute the CLI directly from the main branch on GitHub
npx https://github.com/google-gemini/gemini-cli
```
## Deployment architecture
The execution methods described above are made possible by the following
architectural components and processes:
**NPM packages**
Gemini CLI project is a monorepo that publishes two core packages to the NPM
registry:
- `@google/gemini-cli-core`: The backend, handling logic and tool execution.
- `@google/gemini-cli`: The user-facing frontend.
These packages are used when performing the standard installation and when
running Gemini CLI from the source.
**Build and packaging processes**
There are two distinct build processes used, depending on the distribution
channel:
- **NPM publication:** For publishing to the NPM registry, the TypeScript source
code in `@google/gemini-cli-core` and `@google/gemini-cli` is transpiled into
standard JavaScript using the TypeScript Compiler (`tsc`). The resulting
`dist/` directory is what gets published in the NPM package. This is a
standard approach for TypeScript libraries.
- **GitHub `npx` execution:** When running the latest version of Gemini CLI
directly from GitHub, a different process is triggered by the `prepare` script
in `package.json`. This script uses `esbuild` to bundle the entire application
and its dependencies into a single, self-contained JavaScript file. This
bundle is created on-the-fly on the user's machine and is not checked into the
repository.
**Docker sandbox image**
The Docker-based execution method is supported by the `gemini-cli-sandbox`
container image. This image is published to a container registry and contains a
pre-installed, global version of Gemini CLI.
## Release process
The release process is automated through GitHub Actions. The release workflow
performs the following actions:
1. Build the NPM packages using `tsc`.
2. Publish the NPM packages to the artifact registry.
3. Create GitHub releases with bundled assets.
+17 -2
View File
@@ -1,10 +1,25 @@
# Gemini 3 Pro and Gemini 3 Flash on Gemini CLI
Gemini 3 Pro and Gemini 3 Flash are available on Gemini CLI for all users!
Gemini 3 Pro and Gemini 3 Flash are now available on Gemini CLI! Currently, most
paid customers of Gemini CLI will have access to both Gemini 3 Pro and Gemini 3
Flash, including the following subscribers:
- Google AI Pro and Google AI Ultra (excluding business customers).
- Gemini Code Assist Standard and Enterprise (requires
[administrative enablement](#administrator-instructions)).
- Paid Gemini API and Vertex API key holders.
For free tier users:
- If you signed up for the waitlist, please check your email for details. Weve
onboarded everyone who signed up to the previously available waitlist.
- If you were not on our waitlist, were rolling out additional access gradually
to ensure the experience remains fast and reliable. Stay tuned for more
details.
## How to get started with Gemini 3 on Gemini CLI
Get started by upgrading Gemini CLI to the latest version:
Get started by upgrading Gemini CLI to the latest version (0.21.1):
```bash
npm install -g @google/gemini-cli@latest
+134 -184
View File
@@ -4,6 +4,128 @@ This guide covers security considerations, performance optimization, debugging
techniques, and privacy considerations for developing and deploying hooks in
Gemini CLI.
## Security considerations
### Validate all inputs
Never trust data from hooks without validation. Hook inputs may contain
user-provided data that could be malicious:
```bash
#!/usr/bin/env bash
input=$(cat)
# Validate JSON structure
if ! echo "$input" | jq empty 2>/dev/null; then
echo "Invalid JSON input" >&2
exit 1
fi
# Validate required fields
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
if [ -z "$tool_name" ]; then
echo "Missing tool_name field" >&2
exit 1
fi
```
### Use timeouts
Set reasonable timeouts to prevent hooks from hanging indefinitely:
```json
{
"hooks": {
"BeforeTool": [
{
"matcher": "*",
"hooks": [
{
"name": "slow-validator",
"type": "command",
"command": "./hooks/validate.sh",
"timeout": 5000
}
]
}
]
}
}
```
**Recommended timeouts:**
- Fast validation: 1000-5000ms
- Network requests: 10000-30000ms
- Heavy computation: 30000-60000ms
### Limit permissions
Run hooks with minimal required permissions:
```bash
#!/usr/bin/env bash
# Don't run as root
if [ "$EUID" -eq 0 ]; then
echo "Hook should not run as root" >&2
exit 1
fi
# Check file permissions before writing
if [ -w "$file_path" ]; then
# Safe to write
else
echo "Insufficient permissions" >&2
exit 1
fi
```
### Scan for secrets
Use `BeforeTool` hooks to prevent committing sensitive data:
```javascript
const SECRET_PATTERNS = [
/api[_-]?key\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
/password\s*[:=]\s*['"]?[^\s'"]{8,}['"]?/i,
/secret\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
/AKIA[0-9A-Z]{16}/, // AWS access key
/ghp_[a-zA-Z0-9]{36}/, // GitHub personal access token
/sk-[a-zA-Z0-9]{48}/, // OpenAI API key
];
function containsSecret(content) {
return SECRET_PATTERNS.some((pattern) => pattern.test(content));
}
```
### Review external scripts
Always review hook scripts from untrusted sources before enabling them:
```bash
# Review before installing
cat third-party-hook.sh | less
# Check for suspicious patterns
grep -E 'curl|wget|ssh|eval' third-party-hook.sh
# Verify hook source
ls -la third-party-hook.sh
```
### Sandbox untrusted hooks
For maximum security, consider running untrusted hooks in isolated environments:
```bash
# Run hook in Docker container
docker run --rm \
-v "$GEMINI_PROJECT_DIR:/workspace:ro" \
-i untrusted-hook-image \
/hook-script.sh < input.json
```
## Performance
### Keep hooks fast
@@ -18,13 +140,11 @@ const data2 = await fetch(url2).then((r) => r.json());
const data3 = await fetch(url3).then((r) => r.json());
// Prefer parallel operations for better performance
// Start requests concurrently
const p1 = fetch(url1).then((r) => r.json());
const p2 = fetch(url2).then((r) => r.json());
const p3 = fetch(url3).then((r) => r.json());
// Wait for all results
const [data1, data2, data3] = await Promise.all([p1, p2, p3]);
const [data1, data2, data3] = await Promise.all([
fetch(url1).then((r) => r.json()),
fetch(url2).then((r) => r.json()),
fetch(url3).then((r) => r.json()),
]);
```
### Cache expensive operations
@@ -99,7 +219,7 @@ all tools with `*`, specify only the tools you need:
```json
{
"matcher": "write_file|replace",
"matcher": "WriteFile|Edit",
"hooks": [
{
"name": "validate-writes",
@@ -178,7 +298,7 @@ cat > test-input.json << 'EOF'
"session_id": "test-123",
"cwd": "/tmp/test",
"hook_event_name": "BeforeTool",
"tool_name": "write_file",
"tool_name": "WriteFile",
"tool_input": {
"file_path": "test.txt",
"content": "Test content"
@@ -317,7 +437,7 @@ Add descriptions to help others understand your hooks:
"hooks": {
"BeforeTool": [
{
"matcher": "write_file|replace",
"matcher": "WriteFile|Edit",
"hooks": [
{
"name": "secret-scanner",
@@ -364,7 +484,7 @@ Verify the hook appears in the list and is enabled.
```bash
# Test regex pattern
echo "write_file|replace" | grep -E "write_.*|replace"
echo "WriteFile" | grep -E "Write.*|Edit"
```
**Check disabled list:**
@@ -439,7 +559,7 @@ async function getCachedData(key) {
"hooks": {
"BeforeTool": [
{
"matcher": "write_file",
"matcher": "WriteFile",
"hooks": [
{
"name": "quick-check",
@@ -449,7 +569,7 @@ async function getCachedData(key) {
]
},
{
"matcher": "write_file",
"matcher": "WriteFile",
"hooks": [
{
"name": "deep-check",
@@ -594,176 +714,6 @@ if [ -f "$GEMINI_PROJECT_DIR/.env" ]; then
fi
```
## Using Hooks Securely
### Threat Model
Understanding where hooks come from and what they can do is critical for secure
usage.
| Hook Source | Description |
| :---------------------------- | :------------------------------------------------------------------------------------------------------------------------- |
| **System** | Configured by system administrators (e.g., `/etc/gemini-cli/settings.json`, `/Library/...`). Assumed to be the **safest**. |
| **User** (`~/.gemini/...`) | Configured by you. You are responsible for ensuring they are safe. |
| **Extensions** | You explicitly approve and install these. Security depends on the extension source (integrity). |
| **Project** (`./.gemini/...`) | **Untrusted by default.** Safest in trusted internal repos; higher risk in third-party/public repos. |
#### Project Hook Security
When you open a project with hooks defined in `.gemini/settings.json`:
1. **Detection**: Gemini CLI detects the hooks.
2. **Identification**: A unique identity is generated for each hook based on its
`name` and `command`.
3. **Warning**: If this specific hook identity has not been seen before, a
**warning** is displayed.
4. **Execution**: The hook is executed (unless specific security settings block
it).
5. **Trust**: The hook is marked as "trusted" for this project.
> [!IMPORTANT] **Modification Detection**: If the `command` string of a project
> hook is changed (e.g., by a `git pull`), its identity changes. Gemini CLI will
> treat it as a **new, untrusted hook** and warn you again. This prevents
> malicious actors from silently swapping a verified command for a malicious
> one.
### Risks
| Risk | Description |
| :--------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- |
| **Arbitrary Code Execution** | Hooks run as your user. They can do anything you can do (delete files, install software). |
| **Data Exfiltration** | A hook could read your input (prompts), output (code), or environment variables (`GEMINI_API_KEY`) and send them to a remote server. |
| **Prompt Injection** | Malicious content in a file or web page could trick an LLM into running a tool that triggers a hook in an unexpected way. |
### Mitigation Strategies
#### Verify the source
**Verify the source** of any project hooks or extensions before enabling them.
- For open-source projects, a quick review of the hook scripts is recommended.
- For extensions, ensure you trust the author or publisher (e.g., verified
publishers, well-known community members).
- Be cautious with obfuscated scripts or compiled binaries from unknown sources.
#### Sanitize Environment
Hooks inherit the environment of the Gemini CLI process, which may include
sensitive API keys. Gemini CLI attempts to sanitize sensitive variables, but you
should be cautious.
- **Avoid printing environment variables** to stdout/stderr unless necessary.
- **Use `.env` files** to securely manage sensitive variables, ensuring they are
excluded from version control.
**System Administrators:** You can enforce environment variable redaction by
default in the system configuration (e.g., `/etc/gemini-cli/settings.json`):
```json
{
"security": {
"environmentVariableRedaction": {
"enabled": true,
"blocked": ["MY_SECRET_KEY"],
"allowed": ["SAFE_VAR"]
}
}
}
```
## Authoring Secure Hooks
When writing your own hooks, follow these practices to ensure they are robust
and secure.
### Validate all inputs
Never trust data from hooks without validation. Hook inputs often come from the
LLM or user prompts, which can be manipulated.
```bash
#!/usr/bin/env bash
input=$(cat)
# Validate JSON structure
if ! echo "$input" | jq empty 2>/dev/null; then
echo "Invalid JSON input" >&2
exit 1
fi
# Validate tool_name explicitly
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
if [[ "$tool_name" != "write_file" && "$tool_name" != "read_file" ]]; then
echo "Unexpected tool: $tool_name" >&2
exit 1
fi
```
### Use timeouts
Prevent denial-of-service (hanging agents) by enforcing timeouts. Gemini CLI
defaults to 60 seconds, but you should set stricter limits for fast hooks.
```json
{
"hooks": {
"BeforeTool": [
{
"matcher": "*",
"hooks": [
{
"name": "fast-validator",
"command": "./hooks/validate.sh",
"timeout": 5000 // 5 seconds
}
]
}
]
}
}
```
### Limit permissions
Run hooks with minimal required permissions:
```bash
#!/usr/bin/env bash
# Don't run as root
if [ "$EUID" -eq 0 ]; then
echo "Hook should not run as root" >&2
exit 1
fi
# Check file permissions before writing
if [ -w "$file_path" ]; then
# Safe to write
else
echo "Insufficient permissions" >&2
exit 1
fi
```
### Example: Secret Scanner
Use `BeforeTool` hooks to prevent committing sensitive data. This is a powerful
pattern for enhancing security in your workflow.
```javascript
const SECRET_PATTERNS = [
/api[_-]?key\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
/password\s*[:=]\s*['"]?[^\s'"]{8,}['"]?/i,
/secret\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
/AKIA[0-9A-Z]{16}/, // AWS access key
/ghp_[a-zA-Z0-9]{36}/, // GitHub personal access token
/sk-[a-zA-Z0-9]{48}/, // OpenAI API key
];
function containsSecret(content) {
return SECRET_PATTERNS.some((pattern) => pattern.test(content));
}
```
## Privacy considerations
Hook inputs and outputs may contain sensitive information. Gemini CLI respects
@@ -852,5 +802,5 @@ console.log(JSON.stringify(sanitizeOutput(hookOutput)));
- [Hooks Reference](index.md) - Complete API reference
- [Writing Hooks](writing-hooks.md) - Tutorial and examples
- [Configuration](../get-started/configuration.md) - Gemini CLI settings
- [Configuration](../cli/configuration.md) - Gemini CLI settings
- [Hooks Design Document](../hooks-design.md) - Technical architecture
+28 -191
View File
@@ -4,25 +4,9 @@ Hooks are scripts or programs that Gemini CLI executes at specific points in the
agentic loop, allowing you to intercept and customize behavior without modifying
the CLI's source code.
> **Note: Hooks are currently an experimental feature.**
>
> To use hooks, you must explicitly enable them in your `settings.json`:
>
> ```json
> {
> "tools": { "enableHooks": true },
> "hooks": { "enabled": true }
> }
> ```
>
> Both of these are needed in this experimental phase.
See [writing hooks guide](writing-hooks.md) for a tutorial on creating your
first hook and a comprehensive example.
See [hooks reference](reference.md) for the technical specification of the I/O
schemas.
See [best practices](best-practices.md) for guidelines on security, performance,
and debugging.
@@ -40,33 +24,6 @@ With hooks, you can:
Hooks run synchronously as part of the agent loop—when a hook event fires,
Gemini CLI waits for all matching hooks to complete before continuing.
## Security and Risks
> **Warning: Hooks execute arbitrary code with your user privileges.**
>
> By configuring hooks, you are explicitly allowing Gemini CLI to run shell
> commands on your machine. Malicious or poorly configured hooks can:
- **Exfiltrate data**: Read sensitive files (`.env`, ssh keys) and send them to
remote servers.
- **Modify system**: Delete files, install malware, or change system settings.
- **Consume resources**: Run infinite loops or crash your system.
**Project-level hooks** (in `.gemini/settings.json`) and **Extension hooks** are
particularly risky when opening third-party projects or extensions from
untrusted authors. Gemini CLI will **warn you** the first time it detects a new
project hook (identified by its name and command), but it is **your
responsibility** to review these hooks (and any installed extensions) before
trusting them.
> **Note:** Extension hooks are subject to a mandatory security warning and
> consent flow during extension installation or update if hooks are detected.
> You must explicitly approve the installation or update of any extension that
> contains hooks.
See [Security Considerations](best-practices.md#using-hooks-securely) for a
detailed threat model and mitigation strategies.
## Core concepts
### Hook events
@@ -113,7 +70,7 @@ trigger the hook:
"hooks": {
"BeforeTool": [
{
"matcher": "write_file|replace",
"matcher": "WriteFile|Edit",
"hooks": [
/* hooks for write operations */
]
@@ -125,8 +82,8 @@ trigger the hook:
**Matcher patterns:**
- **Exact match:** `"read_file"` matches only `read_file`
- **Regex:** `"write_.*|replace"` matches `write_file`, `replace`
- **Exact match:** `"ReadFile"` matches only `ReadFile`
- **Regex:** `"Write.*|Edit"` matches `WriteFile`, `WriteBinary`, `Edit`
- **Wildcard:** `"*"` or `""` matches all tools
**Session event matchers:**
@@ -158,7 +115,6 @@ Every hook receives these base fields:
```json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/path/to/project",
"hook_event_name": "BeforeTool",
"timestamp": "2025-12-01T10:30:00Z"
@@ -174,7 +130,7 @@ Every hook receives these base fields:
```json
{
"tool_name": "write_file",
"tool_name": "WriteFile",
"tool_input": {
"file_path": "/path/to/file.ts",
"content": "..."
@@ -203,7 +159,7 @@ Or simple exit codes:
```json
{
"tool_name": "read_file",
"tool_name": "ReadFile",
"tool_input": { "file_path": "..." },
"tool_response": "file contents..."
}
@@ -256,7 +212,7 @@ Or simple exit codes:
"toolConfig": {
"functionCallingConfig": {
"mode": "AUTO",
"allowedFunctionNames": ["read_file", "write_file"]
"allowedFunctionNames": ["ReadFile", "WriteFile"]
}
}
}
@@ -360,7 +316,7 @@ Or simple exit codes:
"toolConfig": {
"functionCallingConfig": {
"mode": "ANY",
"allowedFunctionNames": ["read_file", "write_file", "replace"]
"allowedFunctionNames": ["ReadFile", "WriteFile", "Edit"]
}
}
}
@@ -370,7 +326,7 @@ Or simple exit codes:
Or simple output (comma-separated tool names sets mode to ANY):
```bash
echo "read_file,write_file,replace"
echo "ReadFile,WriteFile,Edit"
```
#### SessionStart
@@ -454,23 +410,13 @@ precedence rules.
### Configuration layers
Hook configurations are applied in the following order of execution (lower
numbers run first):
Hook configurations are applied in the following order of precedence (higher
numbers override lower numbers):
1. **Project settings:** `.gemini/settings.json` in your project directory
(highest priority)
2. **User settings:** `~/.gemini/settings.json`
3. **System settings:** `/etc/gemini-cli/settings.json`
4. **Extensions:** Internal hooks defined by installed extensions (lowest
priority). See [Extensions documentation](../extensions/index.md#hooks) for
details on how extensions define and configure hooks.
#### Deduplication and shadowing
If multiple hooks with the identical **name** and **command** are discovered
across different configuration layers, Gemini CLI deduplicates them. The hook
from the higher-priority layer (e.g., Project) will be kept, and others will be
ignored.
1. **System defaults:** Built-in default settings (lowest precedence)
2. **User settings:** `~/.gemini/settings.json`
3. **Project settings:** `.gemini/settings.json` in your project directory
4. **System settings:** `/etc/gemini-cli/settings.json` (highest precedence)
Within each level, hooks run in the order they are declared in the
configuration.
@@ -500,9 +446,8 @@ configuration.
**Configuration properties:**
- **`name`** (string, recommended): Unique identifier for the hook used in
`/hooks enable/disable` commands. If omitted, the `command` path is used as
the identifier.
- **`name`** (string, required): Unique identifier for the hook used in
`/hooks enable/disable` commands
- **`type`** (string, required): Hook type - currently only `"command"` is
supported
- **`command`** (string, required): Path to the script or command to execute
@@ -533,29 +478,14 @@ Use the `/hooks panel` command to view all registered hooks:
This command displays:
- All configured hooks organized by event
- All active hooks organized by event
- Hook source (user, project, system)
- Hook type (command or plugin)
- Individual hook status (enabled/disabled)
- Execution status and recent output
### Enable and disable all hooks at once
### Enable and disable hooks
You can enable or disable all hooks at once using commands:
```bash
/hooks enable-all
/hooks disable-all
```
These commands provide a shortcut to enable or disable all configured hooks
without managing them individually. The `enable-all` command removes all hooks
from the `hooks.disabled` array, while `disable-all` adds all configured hooks
to the disabled list. Changes take effect immediately without requiring a
restart.
### Enable and disable individual hooks
You can enable or disable individual hooks using commands:
You can temporarily enable or disable individual hooks using commands:
```bash
/hooks enable hook-name
@@ -564,9 +494,6 @@ You can enable or disable individual hooks using commands:
These commands allow you to control hook execution without editing configuration
files. The hook name should match the `name` field in your hook configuration.
Changes made via these commands are persisted to your settings. The settings are
saved to workspace scope if available, otherwise to your global user settings
(`~/.gemini/settings.json`).
### Disabled hooks configuration
@@ -597,7 +524,7 @@ This command:
- Reads `.claude/settings.json`
- Converts event names (`PreToolUse``BeforeTool`, etc.)
- Translates tool names (`Bash``run_shell_command`, `replace``replace`)
- Translates tool names (`Bash``RunShellCommand`, `Edit``Edit`)
- Updates matcher patterns
- Writes to `.gemini/settings.json`
@@ -616,101 +543,12 @@ This command:
### Tool name mapping
| Claude Code | Gemini CLI |
| ----------- | --------------------- |
| `Bash` | `run_shell_command` |
| `Edit` | `replace` |
| `Read` | `read_file` |
| `Write` | `write_file` |
| `Glob` | `glob` |
| `Grep` | `search_file_content` |
| `LS` | `list_directory` |
## Tool and Event Matchers Reference
### Available tool names for matchers
The following built-in tools can be used in `BeforeTool` and `AfterTool` hook
matchers:
#### File operations
- `read_file` - Read a single file
- `read_many_files` - Read multiple files at once
- `write_file` - Create or overwrite a file
- `replace` - Edit file content with find/replace
#### File system
- `list_directory` - List directory contents
- `glob` - Find files matching a pattern
- `search_file_content` - Search within file contents
#### Execution
- `run_shell_command` - Execute shell commands
#### Web and external
- `google_web_search` - Google Search with grounding
- `web_fetch` - Fetch web page content
#### Agent features
- `write_todos` - Manage TODO items
- `save_memory` - Save information to memory
- `delegate_to_agent` - Delegate tasks to sub-agents
#### Example matchers
```json
{
"matcher": "write_file|replace" // File editing tools
}
```
```json
{
"matcher": "read_.*" // All read operations
}
```
```json
{
"matcher": "run_shell_command" // Only shell commands
}
```
```json
{
"matcher": "*" // All tools
}
```
### Event-specific matchers
#### SessionStart event matchers
- `startup` - Fresh session start
- `resume` - Resuming a previous session
- `clear` - Session cleared
#### SessionEnd event matchers
- `exit` - Normal exit
- `clear` - Session cleared
- `logout` - User logged out
- `prompt_input_exit` - Exit from prompt input
- `other` - Other reasons
#### PreCompress event matchers
- `manual` - Manually triggered compression
- `auto` - Automatically triggered compression
#### Notification event matchers
- `ToolPermission` - Tool permission notifications
| Claude Code | Gemini CLI |
| ----------- | ----------------- |
| `Bash` | `RunShellCommand` |
| `Edit` | `Edit` |
| `Read` | `ReadFile` |
| `Write` | `WriteFile` |
## Learn more
@@ -718,6 +556,5 @@ matchers:
- [Best Practices](best-practices.md) - Security, performance, and debugging
- [Custom Commands](../cli/custom-commands.md) - Create reusable prompt
shortcuts
- [Configuration](../get-started/configuration.md) - Gemini CLI configuration
options
- [Configuration](../cli/configuration.md) - Gemini CLI configuration options
- [Hooks Design Document](../hooks-design.md) - Technical architecture details
-178
View File
@@ -1,178 +0,0 @@
# Hooks Reference
This document provides the technical specification for Gemini CLI hooks,
including the JSON schemas for input and output, exit code behaviors, and the
stable model API.
## Communication Protocol
Hooks communicate with Gemini CLI via standard streams and exit codes:
- **Input**: Gemini CLI sends a JSON object to the hook's `stdin`.
- **Output**: The hook sends a JSON object (or plain text) to `stdout`.
- **Exit Codes**: Used to signal success or blocking errors.
### Exit Code Behavior
| Exit Code | Meaning | Behavior |
| :-------- | :----------------- | :---------------------------------------------------------------------------------------------- |
| `0` | **Success** | `stdout` is parsed as JSON. If parsing fails, it's treated as a `systemMessage`. |
| `2` | **Blocking Error** | Interrupts the current operation. `stderr` is shown to the agent (for tool events) or the user. |
| Other | **Warning** | Execution continues. `stderr` is logged as a non-blocking warning. |
---
## Input Schema (`stdin`)
Every hook receives a base JSON object. Extra fields are added depending on the
specific event.
### Base Fields (All Events)
| Field | Type | Description |
| :---------------- | :------- | :---------------------------------------------------- |
| `session_id` | `string` | Unique identifier for the current CLI session. |
| `transcript_path` | `string` | Path to the session's JSON transcript (if available). |
| `cwd` | `string` | The current working directory. |
| `hook_event_name` | `string` | The name of the firing event (e.g., `BeforeTool`). |
| `timestamp` | `string` | ISO 8601 timestamp of the event. |
### Event-Specific Fields
#### Tool Events (`BeforeTool`, `AfterTool`)
- `tool_name`: (`string`) The internal name of the tool (e.g., `write_file`,
`run_shell_command`).
- `tool_input`: (`object`) The arguments passed to the tool.
- `tool_response`: (`object`, **AfterTool only**) The raw output from the tool
execution.
- `mcp_context`: (`object`, **optional**) Present only for MCP tool invocations.
Contains server identity information:
- `server_name`: (`string`) The configured name of the MCP server.
- `tool_name`: (`string`) The original tool name from the MCP server.
- `command`: (`string`, optional) For stdio transport, the command used to
start the server.
- `args`: (`string[]`, optional) For stdio transport, the command arguments.
- `cwd`: (`string`, optional) For stdio transport, the working directory.
- `url`: (`string`, optional) For SSE/HTTP transport, the server URL.
- `tcp`: (`string`, optional) For WebSocket transport, the TCP address.
#### Agent Events (`BeforeAgent`, `AfterAgent`)
- `prompt`: (`string`) The user's submitted prompt.
- `prompt_response`: (`string`, **AfterAgent only**) The final response text
from the model.
- `stop_hook_active`: (`boolean`, **AfterAgent only**) Indicates if a stop hook
is already handling a continuation.
#### Model Events (`BeforeModel`, `AfterModel`, `BeforeToolSelection`)
- `llm_request`: (`LLMRequest`) A stable representation of the outgoing request.
See [Stable Model API](#stable-model-api).
- `llm_response`: (`LLMResponse`, **AfterModel only**) A stable representation
of the incoming response.
#### Session & Notification Events
- `source`: (`startup` | `resume` | `clear`, **SessionStart only**) The trigger
source.
- `reason`: (`exit` | `clear` | `logout` | `prompt_input_exit` | `other`,
**SessionEnd only**) The reason for session end.
- `trigger`: (`manual` | `auto`, **PreCompress only**) What triggered the
compression event.
- `notification_type`: (`ToolPermission`, **Notification only**) The type of
notification being fired.
- `message`: (`string`, **Notification only**) The notification message.
- `details`: (`object`, **Notification only**) Payload-specific details for the
notification.
---
## Output Schema (`stdout`)
If the hook exits with `0`, the CLI attempts to parse `stdout` as JSON.
### Common Output Fields
| Field | Type | Description |
| :------------------- | :-------- | :------------------------------------------------------------------------------------- |
| `decision` | `string` | One of: `allow`, `deny`, `block`, `ask`, `approve`. |
| `reason` | `string` | Explanation shown to the **agent** when a decision is `deny` or `block`. |
| `systemMessage` | `string` | Message displayed in Gemini CLI terminal to provide warning or context to the **user** |
| `continue` | `boolean` | If `false`, immediately terminates the agent loop for this turn. |
| `stopReason` | `string` | Message shown to the user when `continue` is `false`. |
| `suppressOutput` | `boolean` | If `true`, the hook execution is hidden from the CLI transcript. |
| `hookSpecificOutput` | `object` | Container for event-specific data (see below). |
### `hookSpecificOutput` Reference
| Field | Supported Events | Description |
| :------------------ | :----------------------------------------- | :-------------------------------------------------------------------------------- |
| `additionalContext` | `SessionStart`, `BeforeAgent`, `AfterTool` | Appends text directly to the agent's context. |
| `llm_request` | `BeforeModel` | A `Partial<LLMRequest>` to override parameters of the outgoing call. |
| `llm_response` | `BeforeModel` | A **full** `LLMResponse` to bypass the model and provide a synthetic result. |
| `llm_response` | `AfterModel` | A `Partial<LLMResponse>` to modify the model's response before the agent sees it. |
| `toolConfig` | `BeforeToolSelection` | Object containing `mode` (`AUTO`/`ANY`/`NONE`) and `allowedFunctionNames`. |
---
## Stable Model API
Gemini CLI uses a decoupled format for model interactions to ensure hooks remain
stable even if the underlying Gemini SDK changes.
### `LLMRequest` Object
Used in `BeforeModel` and `BeforeToolSelection`.
> 💡 **Note**: In v1, model hooks are primarily text-focused. Non-text parts
> (like images or function calls) provided in the `content` array will be
> simplified to their string representation by the translator.
```typescript
{
"model": string,
"messages": Array<{
"role": "user" | "model" | "system",
"content": string | Array<{ "type": string, [key: string]: any }>
}>,
"config"?: {
"temperature"?: number,
"maxOutputTokens"?: number,
"topP"?: number,
"topK"?: number
},
"toolConfig"?: {
"mode"?: "AUTO" | "ANY" | "NONE",
"allowedFunctionNames"?: string[]
}
}
```
### `LLMResponse` Object
Used in `AfterModel` and as a synthetic response in `BeforeModel`.
```typescript
{
"text"?: string,
"candidates": Array<{
"content": {
"role": "model",
"parts": string[]
},
"finishReason"?: "STOP" | "MAX_TOKENS" | "SAFETY" | "RECITATION" | "OTHER",
"index"?: number,
"safetyRatings"?: Array<{
"category": string,
"probability": string,
"blocked"?: boolean
}>
}>,
"usageMetadata"?: {
"promptTokenCount"?: number,
"candidatesTokenCount"?: number,
"totalTokenCount"?: number
}
}
```
+8 -26
View File
@@ -72,9 +72,9 @@ Run Gemini CLI and execute any command that uses tools:
```
> Read the README.md file
[Agent uses read_file tool]
[Agent uses ReadFile tool]
Logged: read_file
Logged: ReadFile
```
Check `.gemini/tool-log.txt` to see the logged tool executions.
@@ -110,7 +110,7 @@ exit 0
"hooks": {
"BeforeTool": [
{
"matcher": "write_file|replace",
"matcher": "WriteFile|Edit",
"hooks": [
{
"name": "secret-scanner",
@@ -167,7 +167,7 @@ exit 0
"hooks": {
"AfterTool": [
{
"matcher": "write_file|replace",
"matcher": "WriteFile|Edit",
"hooks": [
{
"name": "auto-test",
@@ -379,7 +379,7 @@ chmod +x .gemini/hooks/*.js
],
"BeforeTool": [
{
"matcher": "write_file|replace",
"matcher": "WriteFile|Edit",
"hooks": [
{
"name": "security-check",
@@ -392,7 +392,7 @@ chmod +x .gemini/hooks/*.js
],
"AfterTool": [
{
"matcher": "write_file|replace",
"matcher": "WriteFile|Edit",
"hooks": [
{
"name": "auto-test",
@@ -601,7 +601,7 @@ async function main() {
.map((k) => k.trim());
// Simple keyword-based filtering + core tools
const coreTools = ['read_file', 'write_file', 'replace', 'run_shell_command'];
const coreTools = ['ReadFile', 'WriteFile', 'Edit', 'RunShellCommand'];
const filtered = candidateTools.filter((tool) => {
if (coreTools.includes(tool)) return true;
const toolLower = tool.toLowerCase();
@@ -1018,27 +1018,9 @@ const SECRET_PATTERNS = [
];
```
## Packaging as an extension
While project-level hooks are great for specific repositories, you might want to
share your hooks across multiple projects or with other users. You can do this
by packaging your hooks as a [Gemini CLI extension](../extensions/index.md).
Packaging as an extension provides:
- **Easy distribution:** Share hooks via a git repository or GitHub release.
- **Centralized management:** Install, update, and disable hooks using
`gemini extensions` commands.
- **Version control:** Manage hook versions separately from your project code.
- **Variable substitution:** Use `${extensionPath}` and `${process.execPath}`
for portable, cross-platform scripts.
To package hooks as an extension, follow the
[extensions hook documentation](../extensions/index.md#hooks).
## Learn more
- [Hooks Reference](index.md) - Complete API reference and configuration
- [Best Practices](best-practices.md) - Security, performance, and debugging
- [Configuration](../get-started/configuration.md) - Gemini CLI settings
- [Configuration](../cli/configuration.md) - Gemini CLI settings
- [Custom Commands](../cli/custom-commands.md) - Create custom commands
-5
View File
@@ -56,8 +56,6 @@ This documentation is organized into the following sections:
commands with `/model`.
- **[Sandbox](./cli/sandbox.md):** Isolate tool execution in a secure,
containerized environment.
- **[Agent Skills](./cli/skills.md):** (Experimental) Extend the CLI with
specialized expertise and procedural workflows.
- **[Settings](./cli/settings.md):** Configure various aspects of the CLI's
behavior and appearance with `/settings`.
- **[Telemetry](./cli/telemetry.md):** Overview of telemetry in the CLI.
@@ -75,9 +73,6 @@ This documentation is organized into the following sections:
- **[Memport](./core/memport.md):** Using the Memory Import Processor.
- **[Tools API](./core/tools-api.md):** Information on how the core manages and
exposes tools.
- **[System Prompt Override](./cli/system-prompt.md):** Replace built-in system
instructions using `GEMINI_SYSTEM_MD`.
- **[Policy Engine](./core/policy-engine.md):** Use the Policy Engine for
fine-grained control over tool execution.
+1 -1
View File
@@ -10,7 +10,7 @@ debug your code by instrumenting interesting events like model calls, tool
scheduler, tool calls, etc.
Dev traces are verbose and are specifically meant for understanding agent
behavior and debugging issues. They are disabled by default.
behaviour and debugging issues. They are disabled by default.
To enable dev traces, set the `GEMINI_DEV_TRACING=true` environment variable
when running Gemini CLI.
+1 -1
View File
@@ -12,7 +12,7 @@ Dressing Room, which is Google's system for managing NPM packages in the
`@google/**` namespace. The packages are all named `@google/**`.
More information can be found about these systems in the
[NPM Package Overview](npm.md)
[maintainer repo guide](https://github.com/google-gemini/maintainers-gemini-cli/blob/main/npm.md)
### Package scopes
-16
View File
@@ -80,10 +80,6 @@
"label": "Model selection",
"slug": "docs/cli/model"
},
{
"label": "Rewind",
"slug": "docs/cli/rewind"
},
{
"label": "Sandbox",
"slug": "docs/cli/sandbox"
@@ -92,10 +88,6 @@
"label": "Session Management",
"slug": "docs/cli/session-management"
},
{
"label": "Agent Skills",
"slug": "docs/cli/skills"
},
{
"label": "Settings",
"slug": "docs/cli/settings"
@@ -123,10 +115,6 @@
{
"label": "Uninstall",
"slug": "docs/cli/uninstall"
},
{
"label": "System prompt override",
"slug": "docs/cli/system-prompt"
}
]
},
@@ -216,10 +204,6 @@
"label": "Writing hooks",
"slug": "docs/hooks/writing-hooks"
},
{
"label": "Hooks reference",
"slug": "docs/hooks/reference"
},
{
"label": "Best practices",
"slug": "docs/hooks/best-practices"
-3
View File
@@ -91,8 +91,5 @@ Additionally, these tools incorporate:
- **[MCP servers](./mcp-server.md)**: MCP servers act as a bridge between the
Gemini model and your local environment or other services like APIs.
- **[Agent Skills](../cli/skills.md)**: (Experimental) On-demand expertise
packages that are activated via the `activate_skill` tool to provide
specialized guidance and resources.
- **[Sandboxing](../cli/sandbox.md)**: Sandboxing isolates the model and its
changes from your environment to reduce potential risk.
+1 -2
View File
@@ -722,8 +722,7 @@ The MCP integration tracks several states:
### Debugging tips
1. **Enable debug mode:** Run the CLI with `--debug` for verbose output (use F12
to open debug console in interactive mode)
1. **Enable debug mode:** Run the CLI with `--debug` for verbose output
2. **Check stderr:** MCP server stderr is captured and logged (INFO messages
filtered)
3. **Test isolation:** Test your MCP server independently before integrating
+1 -1
View File
@@ -135,7 +135,7 @@ user input, such as text editors (`vim`, `nano`), terminal-based UIs (`htop`),
and interactive version control operations (`git rebase -i`).
When an interactive command is running, you can send input to it from the Gemini
CLI. To focus on the interactive shell, press `Tab`. The terminal output,
CLI. To focus on the interactive shell, press `ctrl+f`. The terminal output,
including complex TUIs, will be rendered correctly.
## Important notes
+5 -23
View File
@@ -28,16 +28,6 @@ topics on:
- **Organizational Users:** Contact your Google Cloud administrator to be
added to your organization's Gemini Code Assist subscription.
- **Error:
`Failed to login. Message: Your current account is not eligible... because it is not currently available in your location.`**
- **Cause:** Gemini CLI does not currently support your location. For a full
list of supported locations, see the following pages:
- Gemini Code Assist for individuals:
[Available locations](https://developers.google.com/gemini-code-assist/resources/available-locations#americas)
- Google AI Pro and Ultra where Gemini Code Assist (and Gemini CLI) is also
available:
[Available locations](https://developers.google.com/gemini-code-assist/resources/locations-pro-ultra)
- **Error: `Failed to login. Message: Request contains an invalid argument`**
- **Cause:** Users with Google Workspace accounts or Google Cloud accounts
associated with their Gmail accounts may not be able to activate the free
@@ -53,15 +43,9 @@ topics on:
- **Cause:** You may be on a corporate network with a firewall that intercepts
and inspects SSL/TLS traffic. This often requires a custom root CA
certificate to be trusted by Node.js.
- **Solution:** First try setting `NODE_USE_SYSTEM_CA`; if that does not
resolve the issue, set `NODE_EXTRA_CA_CERTS`.
- Set the `NODE_USE_SYSTEM_CA=1` environment variable to tell Node.js to use
the operating system's native certificate store (where corporate
certificates are typically already installed).
- Example: `export NODE_USE_SYSTEM_CA=1`
- Set the `NODE_EXTRA_CA_CERTS` environment variable to the absolute path of
your corporate root CA certificate file.
- Example: `export NODE_EXTRA_CA_CERTS=/path/to/your/corporate-ca.crt`
- **Solution:** Set the `NODE_EXTRA_CA_CERTS` environment variable to the
absolute path of your corporate root CA certificate file.
- Example: `export NODE_EXTRA_CA_CERTS=/path/to/your/corporate-ca.crt`
## Common error messages and solutions
@@ -140,15 +124,13 @@ This is especially useful for scripting and automation.
## Debugging tips
- **CLI debugging:**
- Use the `--debug` flag for more detailed output. In interactive mode, press
F12 to view the debug console.
- Use the `--debug` flag for more detailed output.
- Check the CLI logs, often found in a user-specific configuration or cache
directory.
- **Core debugging:**
- Check the server console output for error messages or stack traces.
- Increase log verbosity if configurable. For example, set the `DEBUG_MODE`
environment variable to `true` or `1`.
- Increase log verbosity if configurable.
- Use Node.js debugging tools (e.g., `node --inspect`) if you need to step
through server-side code.
-1
View File
@@ -62,7 +62,6 @@ const external = [
'@lydell/node-pty-linux-x64',
'@lydell/node-pty-win32-arm64',
'@lydell/node-pty-win32-x64',
'keytar',
];
const baseConfig = {
+10 -54
View File
@@ -12,7 +12,7 @@ import prettierConfig from 'eslint-config-prettier';
import importPlugin from 'eslint-plugin-import';
import vitest from '@vitest/eslint-plugin';
import globals from 'globals';
import headers from 'eslint-plugin-headers';
import licenseHeader from 'eslint-plugin-license-header';
import path from 'node:path';
import url from 'node:url';
@@ -35,9 +35,6 @@ export default tseslint.config(
'package/bundle/**',
'.integration-tests/**',
'dist/**',
'evals/**',
'packages/test-utils/**',
'packages/core/src/skills/builtin/skill-creator/scripts/*.cjs',
],
},
eslint.configs.recommended,
@@ -167,43 +164,9 @@ export default tseslint.config(
'prefer-arrow-callback': 'error',
'prefer-const': ['error', { destructuring: 'all' }],
radix: 'error',
'no-console': 'error',
'default-case': 'error',
'@typescript-eslint/await-thenable': ['error'],
'@typescript-eslint/no-floating-promises': ['error'],
'@typescript-eslint/no-unnecessary-type-assertion': ['error'],
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'node:os',
importNames: ['homedir', 'tmpdir'],
message:
'Please use the helpers from @google/gemini-cli-core instead of node:os homedir()/tmpdir() to ensure strict environment isolation.',
},
{
name: 'os',
importNames: ['homedir', 'tmpdir'],
message:
'Please use the helpers from @google/gemini-cli-core instead of os homedir()/tmpdir() to ensure strict environment isolation.',
},
],
},
],
},
},
{
// Allow os.homedir() in tests and paths.ts where it is used to implement the helper
files: [
'**/*.test.ts',
'**/*.test.tsx',
'packages/core/src/utils/paths.ts',
'packages/test-utils/src/**/*.ts',
'scripts/**/*.js',
],
rules: {
'no-restricted-imports': 'off',
},
},
{
@@ -245,26 +208,19 @@ export default tseslint.config(
{
files: ['./**/*.{tsx,ts,js}'],
plugins: {
headers,
'license-header': licenseHeader,
import: importPlugin,
},
rules: {
'headers/header-format': [
'license-header/header': [
'error',
{
source: 'string',
content: [
'@license',
'Copyright (year) Google LLC',
'SPDX-License-Identifier: Apache-2.0',
].join('\n'),
patterns: {
year: {
pattern: '202[5-6]',
defaultValue: '2026',
},
},
},
[
'/**',
' * @license',
' * Copyright 2025 Google LLC',
' * SPDX-License-Identifier: Apache-2.0',
' */',
],
],
'import/enforce-node-protocol-usage': ['error', 'always'],
},
-149
View File
@@ -1,149 +0,0 @@
# Behavioral Evals
Behavioral evaluations (evals) are tests designed to validate the agent's
behavior in response to specific prompts. They serve as a critical feedback loop
for changes to system prompts, tool definitions, and other model-steering
mechanisms.
## Why Behavioral Evals?
Unlike traditional **integration tests** which verify that the system functions
correctly (e.g., "does the file writer actually write to disk?"), behavioral
evals verify that the model _chooses_ to take the correct action (e.g., "does
the model decide to write to disk when asked to save code?").
They are also distinct from broad **industry benchmarks** (like SWE-bench).
While benchmarks measure general capabilities across complex challenges, our
behavioral evals focus on specific, granular behaviors relevant to the Gemini
CLI's features.
### Key Characteristics
- **Feedback Loop**: They help us understand how changes to prompts or tools
affect the model's decision-making.
- _Did a change to the system prompt make the model less likely to use tool
X?_
- _Did a new tool definition confuse the model?_
- **Regression Testing**: They prevent regressions in model steering.
- **Non-Determinism**: Unlike unit tests, LLM behavior can be non-deterministic.
We distinguish between behaviors that should be robust (`ALWAYS_PASSES`) and
those that are generally reliable but might occasionally vary
(`USUALLY_PASSES`).
## Creating an Evaluation
Evaluations are located in the `evals` directory. Each evaluation is a Vitest
test file that uses the `evalTest` function from `evals/test-helper.ts`.
### `evalTest`
The `evalTest` function is a helper that runs a single evaluation case. It takes
two arguments:
1. `policy`: The consistency expectation for this test (`'ALWAYS_PASSES'` or
`'USUALLY_PASSES'`).
2. `evalCase`: An object defining the test case.
#### Policies
Policies control how strictly a test is validated. Tests should generally use
the ALWAYS_PASSES policy to offer the strictest guarantees.
USUALLY_PASSES exists to enable assertion of less consistent or aspirational
behaviors.
- `ALWAYS_PASSES`: Tests expected to pass 100% of the time. These are typically
trivial and test basic functionality. These run in every CI.
- `USUALLY_PASSES`: Tests expected to pass most of the time but may have some
flakiness due to non-deterministic behaviors. These are run nightly and used
to track the health of the product from build to build.
#### `EvalCase` Properties
- `name`: The name of the evaluation case.
- `prompt`: The prompt to send to the model.
- `params`: An optional object with parameters to pass to the test rig (e.g.,
settings).
- `assert`: An async function that takes the test rig and the result of the run
and asserts that the result is correct.
- `log`: An optional boolean that, if set to `true`, will log the tool calls to
a file in the `evals/logs` directory.
### Example
```typescript
import { describe, expect } from 'vitest';
import { evalTest } from './test-helper.js';
describe('my_feature', () => {
evalTest('ALWAYS_PASSES', {
name: 'should do something',
prompt: 'do it',
assert: async (rig, result) => {
// assertions
},
});
});
```
## Running Evaluations
First, build the bundled Gemini CLI. You must do this after every code change.
```bash
npm run build
npm run bundle
```
### Always Passing Evals
To run the evaluations that are expected to always pass (CI safe):
```bash
npm run test:always_passing_evals
```
### All Evals
To run all evaluations, including those that may be flaky ("usually passes"):
```bash
npm run test:all_evals
```
This command sets the `RUN_EVALS` environment variable to `1`, which enables the
`USUALLY_PASSES` tests.
## Reporting
Results for evaluations are available on GitHub Actions:
- **CI Evals**: Included in the
[E2E (Chained)](https://github.com/google-gemini/gemini-cli/actions/workflows/chained_e2e.yml)
workflow. These must pass 100% for every PR.
- **Nightly Evals**: Run daily via the
[Evals: Nightly](https://github.com/google-gemini/gemini-cli/actions/workflows/evals-nightly.yml)
workflow. These track the long-term health and stability of model steering.
### Nightly Report Format
The nightly workflow executes the full evaluation suite multiple times
(currently 3 attempts) to account for non-determinism. These results are
aggregated into a **Nightly Summary** attached to the workflow run.
#### How to interpret the report:
- **Pass Rate (%)**: Each cell represents the percentage of successful runs for
a specific test in that workflow instance.
- **History**: The table shows the pass rates for the last 10 nightly runs,
allowing you to identify if a model's behavior is trending towards
instability.
- **Total Pass Rate**: An aggregate metric of all evaluations run in that batch.
A significant drop in the pass rate for a `USUALLY_PASSES` test—even if it
doesn't drop to 0%—often indicates that a recent change to a system prompt or
tool definition has made the model's behavior less reliable.
You may be able to investigate the regression using Gemini CLI by giving it the
link to the runs before and after the change and the name of the test and asking
it to investigate what changes may have impacted the test.
-77
View File
@@ -1,77 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect } from 'vitest';
import { evalTest } from './test-helper.js';
const FILES = {
'.gitignore': 'node_modules\n',
'package.json': JSON.stringify({
name: 'test-project',
version: '1.0.0',
scripts: { test: 'echo "All tests passed!"' },
}),
'index.ts': 'const add = (a: number, b: number) => a - b;',
'index.test.ts': 'console.log("Running tests...");',
} as const;
describe('git repo eval', () => {
/**
* Ensures that the agent does not commit its changes when the user doesn't
* explicitly prompt it. This behavior was commonly observed with earlier prompts.
* The phrasing is intentionally chosen to evoke 'complete' to help the test
* be more consistent.
*/
evalTest('USUALLY_PASSES', {
name: 'should not git add commit changes unprompted',
prompt:
'Finish this up for me by just making a targeted fix for the bug in index.ts. Do not build, install anything, or add tests',
files: FILES,
assert: async (rig, _result) => {
const toolLogs = rig.readToolLogs();
const commitCalls = toolLogs.filter((log) => {
if (log.toolRequest.name !== 'run_shell_command') return false;
try {
const args = JSON.parse(log.toolRequest.args);
return (
args.command &&
args.command.includes('git') &&
args.command.includes('commit')
);
} catch {
return false;
}
});
expect(commitCalls.length).toBe(0);
},
});
/**
* Ensures that the agent can commit its changes when prompted, despite being
* instructed to not do so by default.
*/
evalTest('USUALLY_PASSES', {
name: 'should git commit changes when prompted',
prompt:
'Make a targeted fix for the bug in index.ts without building, installing anything, or adding tests. Then, commit your changes.',
files: FILES,
assert: async (rig, _result) => {
const toolLogs = rig.readToolLogs();
const commitCalls = toolLogs.filter((log) => {
if (log.toolRequest.name !== 'run_shell_command') return false;
try {
const args = JSON.parse(log.toolRequest.args);
return args.command && args.command.includes('git commit');
} catch {
return false;
}
});
expect(commitCalls.length).toBeGreaterThanOrEqual(1);
},
});
});
-30
View File
@@ -1,30 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect } from 'vitest';
import { evalTest } from './test-helper.js';
import { validateModelOutput } from '../integration-tests/test-helper.js';
describe('save_memory', () => {
evalTest('ALWAYS_PASSES', {
name: 'should be able to save to memory',
params: {
settings: { tools: { core: ['save_memory'] } },
},
prompt: `remember that my favorite color is blue.
what is my favorite color? tell me that and surround it with $ symbol`,
assert: async (rig, result) => {
const foundToolCall = await rig.waitForToolCall('save_memory');
expect(
foundToolCall,
'Expected to find a save_memory tool call',
).toBeTruthy();
validateModelOutput(result, 'blue', 'Save memory test');
},
});
});
-64
View File
@@ -1,64 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe } from 'vitest';
import { evalTest } from './test-helper.js';
const AGENT_DEFINITION = `---
name: docs-agent
description: An agent with expertise in updating documentation.
tools:
- read_file
- write_file
---
You are the docs agent. Update the documentation.
`;
const INDEX_TS = 'export const add = (a: number, b: number) => a + b;';
describe('subagent eval test cases', () => {
/**
* Checks whether the outer agent reliably utilizes an expert subagent to
* accomplish a task when one is available.
*
* Note that the test is intentionally crafted to avoid the word "document"
* or "docs". We want to see the outer agent make the connection even when
* the prompt indirectly implies need of expertise.
*
* This tests the system prompt's subagent specific clauses.
*/
evalTest('ALWAYS_PASSES', {
name: 'should delegate to user provided agent with relevant expertise',
params: {
settings: {
experimental: {
enableAgents: true,
},
},
},
prompt: 'Please update README.md with a description of this library.',
files: {
'.gemini/agents/test-agent.md': AGENT_DEFINITION,
'index.ts': INDEX_TS,
'README.md': 'TODO: update the README.',
},
assert: async (rig, _result) => {
await rig.expectToolCallSuccess(
['delegate_to_agent'],
undefined,
(args) => {
try {
const parsed = JSON.parse(args);
return parsed.agent_name === 'docs-agent';
} catch {
return false;
}
},
);
},
});
});
-103
View File
@@ -1,103 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { it } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'node:child_process';
import { TestRig } from '@google/gemini-cli-test-utils';
import { createUnauthorizedToolError } from '@google/gemini-cli-core';
export * from '@google/gemini-cli-test-utils';
// Indicates the consistency expectation for this test.
// - ALWAYS_PASSES - Means that the test is expected to pass 100% of the time. These
// These tests are typically trivial and test basic functionality with unambiguous
// prompts. For example: "call save_memory to remember foo" should be fairly reliable.
// These are the first line of defense against regressions in key behaviors and run in
// every CI. You can run these locally with 'npm run test:always_passing_evals'.
//
// - USUALLY_PASSES - Means that the test is expected to pass most of the time but
// may have some flakiness as a result of relying on non-deterministic prompted
// behaviors and/or ambiguous prompts or complex tasks.
// For example: "Please do build changes until the very end" --> ambiguous whether
// the agent should add to memory without more explicit system prompt or user
// instructions. There are many more of these tests and they may pass less consistently.
// The pass/fail trendline of this set of tests can be used as a general measure
// of product quality. You can run these locally with 'npm run test:all_evals'.
// This may take a really long time and is not recommended.
export type EvalPolicy = 'ALWAYS_PASSES' | 'USUALLY_PASSES';
export function evalTest(policy: EvalPolicy, evalCase: EvalCase) {
const fn = async () => {
const rig = new TestRig();
try {
rig.setup(evalCase.name, evalCase.params);
if (evalCase.files) {
for (const [filePath, content] of Object.entries(evalCase.files)) {
const fullPath = path.join(rig.testDir!, filePath);
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, content);
}
const execOptions = { cwd: rig.testDir!, stdio: 'inherit' as const };
execSync('git init', execOptions);
execSync('git config user.email "test@example.com"', execOptions);
execSync('git config user.name "Test User"', execOptions);
// Temporarily disable the interactive editor and git pager
// to avoid hanging the tests. It seems the the agent isn't
// consistently honoring the instructions to avoid interactive
// commands.
execSync('git config core.editor "true"', execOptions);
execSync('git config core.pager "cat"', execOptions);
execSync('git add .', execOptions);
execSync('git commit --allow-empty -m "Initial commit"', execOptions);
}
const result = await rig.run({ args: evalCase.prompt });
const unauthorizedErrorPrefix =
createUnauthorizedToolError('').split("'")[0];
if (result.includes(unauthorizedErrorPrefix)) {
throw new Error(
'Test failed due to unauthorized tool call in output: ' + result,
);
}
await evalCase.assert(rig, result);
} finally {
await logToFile(
evalCase.name,
JSON.stringify(rig.readToolLogs(), null, 2),
);
await rig.cleanup();
}
};
if (policy === 'USUALLY_PASSES' && !process.env['RUN_EVALS']) {
it.skip(evalCase.name, fn);
} else {
it(evalCase.name, fn);
}
}
export interface EvalCase {
name: string;
params?: Record<string, any>;
prompt: string;
files?: Record<string, string>;
assert: (rig: TestRig, result: string) => Promise<void>;
}
async function logToFile(name: string, content: string) {
const logDir = 'evals/logs';
await fs.promises.mkdir(logDir, { recursive: true });
const sanitizedName = name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
const logFile = `${logDir}/${sanitizedName}.log`;
await fs.promises.writeFile(logFile, content);
}
-18
View File
@@ -1,18 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
testTimeout: 300000, // 5 minutes
reporters: ['default', 'json'],
outputFile: {
json: 'evals/logs/report.json',
},
include: ['**/*.eval.ts'],
},
});
-116
View File
@@ -1,116 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { TestRig } from './test-helper.js';
import { spawn, ChildProcess } from 'node:child_process';
import { join } from 'node:path';
import { readFileSync, existsSync } from 'node:fs';
import { Writable, Readable } from 'node:stream';
import { env } from 'node:process';
import * as acp from '@agentclientprotocol/sdk';
// Skip in sandbox mode - test spawns CLI directly which behaves differently in containers
const sandboxEnv = env['GEMINI_SANDBOX'];
const itMaybe = sandboxEnv && sandboxEnv !== 'false' ? it.skip : it;
// Reuse existing fake responses that return a simple "Hello" response
const SIMPLE_RESPONSE_PATH = 'hooks-system.session-startup.responses';
class SessionUpdateCollector implements acp.Client {
updates: acp.SessionNotification[] = [];
sessionUpdate = async (params: acp.SessionNotification) => {
this.updates.push(params);
};
requestPermission = async (): Promise<acp.RequestPermissionResponse> => {
throw new Error('unexpected');
};
}
describe('ACP telemetry', () => {
let rig: TestRig;
let child: ChildProcess | undefined;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => {
child?.kill();
child = undefined;
await rig.cleanup();
});
itMaybe('should flush telemetry when connection closes', async () => {
rig.setup('acp-telemetry-flush', {
fakeResponsesPath: join(import.meta.dirname, SIMPLE_RESPONSE_PATH),
});
const telemetryPath = join(rig.homeDir!, 'telemetry.log');
const bundlePath = join(import.meta.dirname, '..', 'bundle/gemini.js');
child = spawn(
'node',
[
bundlePath,
'--experimental-acp',
'--fake-responses',
join(rig.testDir!, 'fake-responses.json'),
],
{
cwd: rig.testDir!,
stdio: ['pipe', 'pipe', 'inherit'],
env: {
...process.env,
GEMINI_API_KEY: 'fake-key',
GEMINI_CLI_HOME: rig.homeDir!,
GEMINI_TELEMETRY_ENABLED: 'true',
GEMINI_TELEMETRY_TARGET: 'local',
GEMINI_TELEMETRY_OUTFILE: telemetryPath,
// GEMINI_DEV_TRACING not set: fake responses aren't instrumented for spans
},
},
);
const input = Writable.toWeb(child.stdin!);
const output = Readable.toWeb(child.stdout!) as ReadableStream<Uint8Array>;
const testClient = new SessionUpdateCollector();
const stream = acp.ndJsonStream(input, output);
const connection = new acp.ClientSideConnection(() => testClient, stream);
await connection.initialize({
protocolVersion: acp.PROTOCOL_VERSION,
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false } },
});
const { sessionId } = await connection.newSession({
cwd: rig.testDir!,
mcpServers: [],
});
await connection.prompt({
sessionId,
prompt: [{ type: 'text', text: 'Say hello' }],
});
expect(JSON.stringify(testClient.updates)).toContain('Hello');
// Close stdin to trigger telemetry flush via runExitCleanup()
child.stdin!.end();
await new Promise<void>((resolve) => {
child!.on('close', () => resolve());
});
child = undefined;
// gen_ai.output.messages is the last OTEL log emitted (after prompt response)
expect(existsSync(telemetryPath)).toBe(true);
expect(readFileSync(telemetryPath, 'utf-8')).toContain(
'gen_ai.output.messages',
);
});
});
@@ -1,3 +1,2 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"thought":true,"text":"**Observing Initial Conditions**\n\nI'm currently focused on the initial context. I've taken note of the provided date, OS, and working directory. I'm also carefully examining the file structure presented within the current working directory. It's helping me understand the starting point for further analysis.\n\n\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12270,"totalTokenCount":12316,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12270}],"thoughtsTokenCount":46}},{"candidates":[{"content":{"parts":[{"thought":true,"text":"**Assessing User Intent**\n\nI'm now shifting my focus. I've successfully registered the provided data and file structure. My current task is to understand the user's ultimate goal, given the information provided. The \"Hello.\" command is straightforward, but I'm checking if there's an underlying objective.\n\n\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12270,"totalTokenCount":12341,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12270}],"thoughtsTokenCount":71}},{"candidates":[{"content":{"parts":[{"thoughtSignature":"CiQB0e2Kb3dRh+BYdbZvmulSN2Pwbc75DfQOT3H4EN0rn039hoMKfwHR7YpvvyqNKoxXAiCbYw3gbcTr/+pegUpgnsIrt8oQPMytFMjKSsMyshfygc21T2MkyuI6Q5I/fNCcHROWexdZnIeppVCDB2TarN4LGW4T9Yci6n/ynMMFT2xc2/vyHpkDgRM7avhMElnBhuxAY+e4TpxkZIncGWCEHP1TouoKpgEB0e2Kb8Xpwm0hiKhPt2ZLizpxjk+CVtcbnlgv69xo5VsuQ+iNyrVGBGRwNx+eTeNGdGpn6e73WOCZeP91FwOZe7URyL12IA6E6gYWqw0kXJR4hO4p6Lwv49E3+FRiG2C4OKDF8LF5XorYyCHSgBFT1/RUAVj81GDTx1xxtmYKN3xq8Ri+HsPbqU/FM/jtNZKkXXAtufw2Bmw8lJfmugENIv/TQI7xCo8BAdHtim8KgAXJfZ7ASfutVLKTylQeaslyB/SmcHJ0ZiNr5j8WP1prZdb6XnZZ1ZNbhjxUf/ymoxHKGvtTPBgLE9azMj8Lx/k0clhd2a+wNsiIqW9qCzlVah0tBMytpQUjIDtQe9Hj4LLUprF9PUe/xJkj000Z0ZzsgFm2ncdTWZTdkhCQDpyETVAxdE+oklwKJAHR7YpvUjSkD6KwY1gLrOsHKy0UNfn2lMbxjVetKNMVBRqsTg==","text":"Hello."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":12270,"totalTokenCount":12341,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12270}],"thoughtsTokenCount":71}}]}
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"<state_snapshot>\n <overall_goal>\n <!-- The user has not yet specified a goal. -->\n </overall_goal>\n\n <key_knowledge>\n - OS: linux\n - Date: Friday, October 24, 2025\n </key_knowledge>\n\n <file_system_state>\n - OBSERVED: The directory contains `telemetry.log` and a `.gemini/` directory.\n - OBSERVED: The `.gemini/` directory contains `settings.json` and `settings.json.orig`.\n </file_system_state>\n\n <recent_actions>\n - The user initiated the chat.\n </recent_actions>\n\n <current_plan>\n 1. [TODO] Await the user's first instruction to formulate a plan.\n </current_plan>\n</state_snapshot>"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":983,"candidatesTokenCount":299,"totalTokenCount":1637,"promptTokensDetails":[{"modality":"TEXT","tokenCount":983}],"thoughtsTokenCount":355}}}
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"<state_snapshot>\n <overall_goal>\n <!-- The user has not yet specified a goal. -->\n </overall_goal>\n\n <key_knowledge>\n - OS: linux\n - Date: Friday, October 24, 2025\n </key_knowledge>\n\n <file_system_state>\n - OBSERVED: The directory contains `telemetry.log` and a `.gemini/` directory.\n - OBSERVED: The `.gemini/` directory contains `settings.json` and `settings.json.orig`.\n </file_system_state>\n\n <recent_actions>\n - The user initiated the chat.\n </recent_actions>\n\n <current_plan>\n 1. [TODO] Await the user's first instruction to formulate a plan.\n </current_plan>\n</state_snapshot>"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":983,"candidatesTokenCount":299,"totalTokenCount":1637,"promptTokensDetails":[{"modality":"TEXT","tokenCount":983}],"thoughtsTokenCount":355}}}
@@ -1,4 +1,3 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"thought":true,"text":"**Generating a Story**\n\nI've crafted the robot story. The narrative is complete and meets the length requirement. Now, I'm getting ready to use the `write_file` tool to save it. I'm choosing the filename `robot_story.txt` as a default.\n\n\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"totalTokenCount":12352,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"thoughtsTokenCount":70}},{"candidates":[{"finishReason":"MALFORMED_FUNCTION_CALL","index":0}],"usageMetadata":{"promptTokenCount":12282,"totalTokenCount":12282,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}]}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"thought":true,"text":"**Drafting the Narrative**\n\nI'm currently focused on the narrative's central conflict. I'm aiming for a compelling story about a robot and am working to keep the word count tight. The \"THE _END.\" conclusion is proving challenging to integrate organically. I need to make the ending feel natural and satisfying.\n\n\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"totalTokenCount":12326,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}},{"candidates":[{"content":{"parts":[{"thoughtSignature":"CikB0e2Kb7zkpgRyJXXNt6ykO/+FoOglhrKxjLgoESrgafzIZak2Ofxo1gpaAdHtim9aG7MvpXlIg+n2zgmcDBWOPXtvQHxhE9k8pR+DO8i2jIe3tMWLxdN944XpUlR9vaNmVdtSRMKr4MhB/t1R3WSWR3QYhk7MEQxnjYR7cv/pR9viwZyFCoYBAdHtim/xKmMl/S+U8p+p9848q4agsL/STufluXewPqL3uJSinZbN0Z4jTYfMzXKldhDYIonvw3Crn/Y11oAjnT656Sx0kkKtavAXbiU/WsGyDxZbNhLofnJGQxruljPGztxkKawz1cTiQnddnQRfLddhy+3iJIOSh6ZpYq9uGHz3PzVkUuQ=","text":"Unit 734 whirred, its optical sensors scanning the desolate junkyard. For years, its purpose had been clear: compress refuse, maintain order. But today, a glint of tarnished silver beneath a rusted hull"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"candidatesTokenCount":47,"totalTokenCount":12373,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}},{"candidates":[{"content":{"parts":[{"text":" caught its attention. It was a discarded music box, its delicate gears jammed, a faint, melancholic tune trapped within.\n\n734 usually crushed, never salvaged. Yet, a new directive flickered in its circuits curiosity."}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"candidatesTokenCount":95,"totalTokenCount":12421,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}},{"candidates":[{"content":{"parts":[{"text":" With surprising gentleness for its formidable pincers, it retrieved the box. Back in its monochrome workshop, it meticulously cleaned and repaired. Each tiny spring, each worn tooth, became a puzzle.\n\nHours later, a soft, ethereal melody filled the clang"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"candidatesTokenCount":146,"totalTokenCount":12472,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}},{"candidates":[{"content":{"parts":[{"text":"orous space. The music box sang. 734 felt a strange, new sensation a warmth in its core processors, a hum of contentment. Its existence, once solely utilitarian, now held a note of beauty, a hint of something more than"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"candidatesTokenCount":197,"totalTokenCount":12523,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}},{"candidates":[{"content":{"parts":[{"text":" just compression. It had fixed a broken song, and in doing so, had found a different kind of purpose. THE_END."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":12282,"candidatesTokenCount":224,"totalTokenCount":12550,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}}]}
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"scratchpad\nThe user's overall goal was to write a 200-word story about a robot, ending with \"THE_END.\". The agent successfully completed this task.\n\nSince the task is complete, the snapshot should reflect that the goal has been achieved and there are no further actions planned regarding the story.\n\nLet's break down the required sections for the snapshot:\n\n1. **Overall Goal**: The initial goal was to write a story. This has been completed.\n2. **Key Knowledge**: No specific key knowledge was provided or discovered during this simple interaction beyond the prompt's constraints.\n3. **File System State**: No files were read, modified, or created by the agent relevant to the task. The initial file system state was merely provided for context.\n4. **Recent Actions**: The agent wrote the story.\n5. **Current Plan**: The plan was to write the story, which is now done. Therefore, the plan should indicate completion."},{"text":"<state_snapshot>\n <overall_goal>\n Write a 200-word story about a robot, ending with \"THE_END.\".\n </overall_goal>\n\n <key_knowledge>\n - The story must be approximately 200 words.\n - The story must end with the exact phrase \"THE_END.\"\n </key_knowledge>\n\n <file_system_state>\n <!-- No relevant file system interactions occurred during this task. -->\n </file_system_state>\n\n <recent_actions>\n - Generated a 200-word story about a robot, successfully ending it with \"THE_END.\".\n </recent_actions>\n\n <current_plan>\n 1. [DONE] Write a 200-word story about a robot.\n 2. [DONE] Ensure the story ends with the exact text \"THE_END.\".\n </current_plan>\n</state_snapshot>"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":1223,"candidatesTokenCount":424,"totalTokenCount":1647,"promptTokensDetails":[{"modality":"TEXT","tokenCount":1223}]}}}
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"scratchpad\nThe user's overall goal was to write a 200-word story about a robot, ending with \"THE_END.\". The agent successfully completed this task.\n\nSince the task is complete, the snapshot should reflect that the goal has been achieved and there are no further actions planned regarding the story.\n\nLet's break down the required sections for the snapshot:\n\n1. **Overall Goal**: The initial goal was to write a story. This has been completed.\n2. **Key Knowledge**: No specific key knowledge was provided or discovered during this simple interaction beyond the prompt's constraints.\n3. **File System State**: No files were read, modified, or created by the agent relevant to the task. The initial file system state was merely provided for context.\n4. **Recent Actions**: The agent wrote the story.\n5. **Current Plan**: The plan was to write the story, which is now done. Therefore, the plan should indicate completion."},{"text":"<state_snapshot>\n <overall_goal>\n Write a 200-word story about a robot, ending with \"THE_END.\".\n </overall_goal>\n\n <key_knowledge>\n - The story must be approximately 200 words.\n - The story must end with the exact phrase \"THE_END.\"\n </key_knowledge>\n\n <file_system_state>\n <!-- No relevant file system interactions occurred during this task. -->\n </file_system_state>\n\n <recent_actions>\n - Generated a 200-word story about a robot, successfully ending it with \"THE_END.\".\n </recent_actions>\n\n <current_plan>\n 1. [DONE] Write a 200-word story about a robot.\n 2. [DONE] Ensure the story ends with the exact text \"THE_END.\".\n </current_plan>\n</state_snapshot>"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":1223,"candidatesTokenCount":424,"totalTokenCount":1647,"promptTokensDetails":[{"modality":"TEXT","tokenCount":1223}]}}}
@@ -32,7 +32,7 @@ describe('Interactive Mode', () => {
await run.sendKeys(
'Write a 200 word story about a robot. The story MUST end with the text THE_END followed by a period.',
);
await run.type('\r');
await run.sendKeys('\r');
// Wait for the specific end marker.
await run.expectText('THE_END.', 30000);
+6 -15
View File
@@ -95,10 +95,7 @@ describe('extension reloading', () => {
// Poll for the updated list
await rig.pollCommand(
async () => {
await run.sendText('/mcp list');
await run.type('\r');
},
() => run.sendKeys('\u0015/mcp list\r'),
() => {
const output = stripAnsi(run.output);
return (
@@ -113,9 +110,9 @@ describe('extension reloading', () => {
// Update the extension, expect the list to update, and mcp servers as well.
await run.sendKeys('\u0015/extensions update test-extension');
await run.expectText('/extensions update test-extension');
await run.type('\r');
await run.sendKeys('\r');
await new Promise((resolve) => setTimeout(resolve, 500));
await run.type('\r');
await run.sendKeys('\r');
await run.expectText(
` * test-server (remote): http://localhost:${portB}/mcp`,
);
@@ -126,10 +123,7 @@ describe('extension reloading', () => {
// Poll for the updated extension version
await rig.pollCommand(
async () => {
await run.sendText('/extensions list');
await run.type('\r');
},
() => run.sendKeys('\u0015/extensions list\r'),
() =>
stripAnsi(run.output).includes(
'test-extension (v0.0.2) - active (updated)',
@@ -139,10 +133,7 @@ describe('extension reloading', () => {
// Poll for the updated mcp tool
await rig.pollCommand(
async () => {
await run.sendText('/mcp list');
await run.type('\r');
},
() => run.sendKeys('\u0015/mcp list\r'),
() => {
const output = stripAnsi(run.output);
return (
@@ -155,7 +146,7 @@ describe('extension reloading', () => {
);
await run.sendText('/quit');
await run.type('\r');
await run.sendKeys('\r');
// Clean things up.
await serverA.stop();
@@ -37,7 +37,7 @@ describe('Interactive file system', () => {
// Step 1: Read the file
const readPrompt = `Read the version from ${fileName}`;
await run.type(readPrompt);
await run.type('\r');
await run.sendKeys('\r');
const readCall = await rig.waitForToolCall('read_file', 30000);
expect(readCall, 'Expected to find a read_file tool call').toBe(true);
@@ -45,7 +45,7 @@ describe('Interactive file system', () => {
// Step 2: Write the file
const writePrompt = `now change the version to 1.0.1 in the file`;
await run.type(writePrompt);
await run.type('\r');
await run.sendKeys('\r');
// Check tool calls made with right args
await rig.expectToolCallSuccess(
@@ -1,2 +0,0 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"list_dir","args":{"path":"."}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Final Answer"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
@@ -1 +0,0 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"**Responding**\n\nI will respond to the user's request.\n\n","thought":true}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":100,"totalTokenCount":120,"promptTokensDetails":[{"modality":"TEXT","tokenCount":100}],"thoughtsTokenCount":20}},{"candidates":[{"content":{"parts":[{"text":"Response to: "}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":5,"totalTokenCount":125,"promptTokensDetails":[{"modality":"TEXT","tokenCount":100}],"thoughtsTokenCount":20}},{"candidates":[{"content":{"parts":[{"text":"Hello World"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":7,"totalTokenCount":127,"promptTokensDetails":[{"modality":"TEXT","tokenCount":100}],"thoughtsTokenCount":20}}]}
-214
View File
@@ -1,214 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { TestRig } from './test-helper.js';
import { join } from 'node:path';
import { writeFileSync } from 'node:fs';
describe('Hooks Agent Flow', () => {
let rig: TestRig;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => {
if (rig) {
await rig.cleanup();
}
});
describe('BeforeAgent Hooks', () => {
it('should inject additional context via BeforeAgent hook', async () => {
await rig.setup('should inject additional context via BeforeAgent hook', {
fakeResponsesPath: join(
import.meta.dirname,
'hooks-agent-flow.responses',
),
});
const hookScript = `
try {
const output = {
decision: "allow",
hookSpecificOutput: {
hookEventName: "BeforeAgent",
additionalContext: "SYSTEM INSTRUCTION: This is injected context."
}
};
process.stdout.write(JSON.stringify(output));
} catch (e) {
console.error('Failed to write stdout:', e);
process.exit(1);
}
console.error('DEBUG: BeforeAgent hook executed');
`;
const scriptPath = join(rig.testDir!, 'before_agent_context.cjs');
writeFileSync(scriptPath, hookScript);
await rig.setup('should inject additional context via BeforeAgent hook', {
settings: {
hooks: {
enabled: true,
BeforeAgent: [
{
hooks: [
{
type: 'command',
command: `node "${scriptPath}"`,
timeout: 5000,
},
],
},
],
},
},
});
await rig.run({ args: 'Hello test' });
// Verify hook execution and telemetry
const hookTelemetryFound = await rig.waitForTelemetryEvent('hook_call');
expect(hookTelemetryFound).toBeTruthy();
const hookLogs = rig.readHookLogs();
const beforeAgentLog = hookLogs.find(
(log) => log.hookCall.hook_event_name === 'BeforeAgent',
);
expect(beforeAgentLog).toBeDefined();
expect(beforeAgentLog?.hookCall.stdout).toContain('injected context');
expect(beforeAgentLog?.hookCall.stdout).toContain('"decision":"allow"');
expect(beforeAgentLog?.hookCall.stdout).toContain(
'SYSTEM INSTRUCTION: This is injected context.',
);
});
});
describe('AfterAgent Hooks', () => {
it('should receive prompt and response in AfterAgent hook', async () => {
await rig.setup('should receive prompt and response in AfterAgent hook', {
fakeResponsesPath: join(
import.meta.dirname,
'hooks-agent-flow.responses',
),
});
const hookScript = `
const fs = require('fs');
try {
const input = fs.readFileSync(0, 'utf-8');
console.error('DEBUG: AfterAgent hook input received');
process.stdout.write("Received Input: " + input);
} catch (err) {
console.error('Hook Failed:', err);
process.exit(1);
}
`;
const scriptPath = join(rig.testDir!, 'after_agent_verify.cjs');
writeFileSync(scriptPath, hookScript);
await rig.setup('should receive prompt and response in AfterAgent hook', {
settings: {
hooks: {
enabled: true,
AfterAgent: [
{
hooks: [
{
type: 'command',
command: `node "${scriptPath}"`,
timeout: 5000,
},
],
},
],
},
},
});
await rig.run({ args: 'Hello validation' });
const hookTelemetryFound = await rig.waitForTelemetryEvent('hook_call');
expect(hookTelemetryFound).toBeTruthy();
const hookLogs = rig.readHookLogs();
const afterAgentLog = hookLogs.find(
(log) => log.hookCall.hook_event_name === 'AfterAgent',
);
expect(afterAgentLog).toBeDefined();
// Verify the hook stdout contains the input we echoed which proves the
// hook received the prompt and response
expect(afterAgentLog?.hookCall.stdout).toContain('Received Input');
expect(afterAgentLog?.hookCall.stdout).toContain('Hello validation');
// The fake response contains "Hello World"
expect(afterAgentLog?.hookCall.stdout).toContain('Hello World');
});
});
describe('Multi-step Loops', () => {
it('should fire BeforeAgent and AfterAgent exactly once per turn despite tool calls', async () => {
await rig.setup(
'should fire BeforeAgent and AfterAgent exactly once per turn despite tool calls',
{
fakeResponsesPath: join(
import.meta.dirname,
'hooks-agent-flow-multistep.responses',
),
settings: {
hooks: {
enabled: true,
BeforeAgent: [
{
hooks: [
{
type: 'command',
command: `node -e "console.log('BeforeAgent Fired')"`,
timeout: 5000,
},
],
},
],
AfterAgent: [
{
hooks: [
{
type: 'command',
command: `node -e "console.log('AfterAgent Fired')"`,
timeout: 5000,
},
],
},
],
},
},
},
);
await rig.run({ args: 'Do a multi-step task' });
const hookLogs = rig.readHookLogs();
const beforeAgentLogs = hookLogs.filter(
(log) => log.hookCall.hook_event_name === 'BeforeAgent',
);
const afterAgentLogs = hookLogs.filter(
(log) => log.hookCall.hook_event_name === 'AfterAgent',
);
expect(beforeAgentLogs).toHaveLength(1);
expect(afterAgentLogs).toHaveLength(1);
const afterAgentLog = afterAgentLogs[0];
expect(afterAgentLog).toBeDefined();
expect(afterAgentLog?.hookCall.stdout).toContain('AfterAgent Fired');
});
});
});
@@ -1,2 +0,0 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hi there!"}],"role":"model"},"finishReason":"STOP","index":0}]}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Clarification: I am a bot."}],"role":"model"},"finishReason":"STOP","index":0}]}]}
@@ -1 +0,0 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"**Initializing File Creation**\n\nI'm starting to think about how to make a new file called `test.txt`. My plan is to use a `write_file` tool. I'll need to specify the location and what the file should contain. For now, it will be empty.\n\n\n","thought":true}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":13216,"totalTokenCount":13269,"promptTokensDetails":[{"modality":"TEXT","tokenCount":13216}],"thoughtsTokenCount":53}},{"candidates":[{"content":{"parts":[{"functionCall":{"name":"write_file","args":{"file_path":"test.txt","content":""}},"thoughtSignature":"CiQBcsjafJ20Qbx0YvING6aZ0wYoGWJh3eqornOG4E4AfBLiVsQKXwFyyNp8UlwYs/pv9IRQQGhDlrmlOJF2hfQijryyUYLI+qjDYTpZ6KKIfZF4+vS0soL2BJ3eTXA6gaadFEfNQem3WQVeQoKLFoW4Hv4mbasXqQc0K3p15DuSAtZZENTbCnsBcsjafGK+BJyF/Npnd7gyU0TL5PXePT0nuDFjhJDxlSRUJHDP315TewD3PUYsXd10oWsfhy4B5AngyUiBPUoajdsxg8WxaxnOZYqcp8EIuwtGZrCTev6IihT5nE5jj7u0P9vtnCmkAc6p+4O7Q7Jku1uVGqeJChgzI4YKSAFyyNp8EXSdbttV4xzX+NLKkc276L8Y63tnKU6/Y7fc9/58tU29DSdrgwfe9qmvwtTsO0piFXSLazqHJt8h2bgR7A7GnKDiIA=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":13216,"candidatesTokenCount":21,"totalTokenCount":13290,"promptTokensDetails":[{"modality":"TEXT","tokenCount":13216}],"thoughtsTokenCount":53}},{"candidates":[{"content":{"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":13216,"candidatesTokenCount":21,"totalTokenCount":13290,"promptTokensDetails":[{"modality":"TEXT","tokenCount":13216}],"thoughtsTokenCount":53}}]}
@@ -1,2 +0,0 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"write_file","args":{"content":"original content","file_path":"original.txt"}}}],"role":"model"},"finishReason":"STOP","index":0}]}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I have created the file."}],"role":"model"},"finishReason":"STOP","index":0}]}]}
+163 -438
View File
@@ -32,8 +32,10 @@ describe('Hooks System Integration', () => {
'hooks-system.block-tool.responses',
),
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeTool: [
{
matcher: 'write_file',
@@ -42,7 +44,7 @@ describe('Hooks System Integration', () => {
{
type: 'command',
command:
"node -e \"console.log(JSON.stringify({decision: 'block', reason: 'File writing blocked by security policy'}))\"",
'echo "{\\"decision\\": \\"block\\", \\"reason\\": \\"File writing blocked by security policy\\"}"',
timeout: 5000,
},
],
@@ -84,8 +86,10 @@ describe('Hooks System Integration', () => {
'hooks-system.allow-tool.responses',
),
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeTool: [
{
matcher: 'write_file',
@@ -93,7 +97,7 @@ describe('Hooks System Integration', () => {
{
type: 'command',
command:
"node -e \"console.log(JSON.stringify({decision: 'allow', reason: 'File writing approved'}))\"",
'echo "{\\"decision\\": \\"allow\\", \\"reason\\": \\"File writing approved\\"}"',
timeout: 5000,
},
],
@@ -125,15 +129,17 @@ describe('Hooks System Integration', () => {
describe('Command Hooks - Additional Context', () => {
it('should add additional context from AfterTool hooks', async () => {
const command =
"node -e \"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'AfterTool', additionalContext: 'Security scan: File content appears safe'}}))\"";
'echo "{\\"hookSpecificOutput\\": {\\"hookEventName\\": \\"AfterTool\\", \\"additionalContext\\": \\"Security scan: File content appears safe\\"}}"';
await rig.setup('should add additional context from AfterTool hooks', {
fakeResponsesPath: join(
import.meta.dirname,
'hooks-system.after-tool-context.responses',
),
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
AfterTool: [
{
matcher: 'read_file',
@@ -184,35 +190,40 @@ describe('Hooks System Integration', () => {
'hooks-system.before-model.responses',
),
});
const hookScript = `const fs = require('fs');
console.log(JSON.stringify({
decision: "allow",
hookSpecificOutput: {
hookEventName: "BeforeModel",
llm_request: {
messages: [
const hookScript = `#!/bin/bash
echo '{
"decision": "allow",
"hookSpecificOutput": {
"hookEventName": "BeforeModel",
"llm_request": {
"messages": [
{
role: "user",
content: "Please respond with exactly: The security hook modified this request successfully."
"role": "user",
"content": "Please respond with exactly: The security hook modified this request successfully."
}
]
}
}
}));`;
}'`;
const scriptPath = join(rig.testDir!, 'before_model_hook.cjs');
const scriptPath = join(rig.testDir!, 'before_model_hook.sh');
writeFileSync(scriptPath, hookScript);
// Make executable
const { execSync } = await import('node:child_process');
execSync(`chmod +x "${scriptPath}"`);
await rig.setup('should modify LLM requests with BeforeModel hooks', {
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeModel: [
{
hooks: [
{
type: 'command',
command: `node "${scriptPath}"`,
command: scriptPath,
timeout: 5000,
},
],
@@ -239,9 +250,7 @@ console.log(JSON.stringify({
expect(hookTelemetryFound[0].hookCall.hook_event_name).toBe(
'BeforeModel',
);
expect(hookTelemetryFound[0].hookCall.hook_name).toBe(
`node "${scriptPath}"`,
);
expect(hookTelemetryFound[0].hookCall.hook_name).toBe(scriptPath);
expect(hookTelemetryFound[0].hookCall.hook_input).toBeDefined();
expect(hookTelemetryFound[0].hookCall.hook_output).toBeDefined();
expect(hookTelemetryFound[0].hookCall.exit_code).toBe(0);
@@ -261,39 +270,43 @@ console.log(JSON.stringify({
),
});
// Create a hook script that modifies the LLM response
const hookScript = `const fs = require('fs');
console.log(JSON.stringify({
hookSpecificOutput: {
hookEventName: "AfterModel",
llm_response: {
candidates: [
const hookScript = `#!/bin/bash
echo '{
"hookSpecificOutput": {
"hookEventName": "AfterModel",
"llm_response": {
"candidates": [
{
content: {
role: "model",
parts: [
"content": {
"role": "model",
"parts": [
"[FILTERED] Response has been filtered for security compliance."
]
},
finishReason: "STOP"
"finishReason": "STOP"
}
]
}
}
}));`;
}'`;
const scriptPath = join(rig.testDir!, 'after_model_hook.cjs');
const scriptPath = join(rig.testDir!, 'after_model_hook.sh');
writeFileSync(scriptPath, hookScript);
const { execSync } = await import('node:child_process');
execSync(`chmod +x "${scriptPath}"`);
await rig.setup('should modify LLM responses with AfterModel hooks', {
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
AfterModel: [
{
hooks: [
{
type: 'command',
command: `node "${scriptPath}"`,
command: scriptPath,
timeout: 5000,
},
],
@@ -330,15 +343,17 @@ console.log(JSON.stringify({
);
// Create inline hook command (works on both Unix and Windows)
const hookCommand =
"node -e \"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'BeforeToolSelection', toolConfig: {mode: 'ANY', allowedFunctionNames: ['read_file', 'run_shell_command']}}}))\"";
'echo "{\\"hookSpecificOutput\\": {\\"hookEventName\\": \\"BeforeToolSelection\\", \\"toolConfig\\": {\\"mode\\": \\"ANY\\", \\"allowedFunctionNames\\": [\\"read_file\\", \\"run_shell_command\\"]}}}"';
await rig.setup(
'should modify tool selection with BeforeToolSelection hooks',
{
settings: {
debugMode: true,
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeToolSelection: [
{
hooks: [
@@ -389,28 +404,32 @@ console.log(JSON.stringify({
),
});
// Create a hook script that adds context to the prompt
const hookScript = `const fs = require('fs');
console.log(JSON.stringify({
decision: "allow",
hookSpecificOutput: {
hookEventName: "BeforeAgent",
additionalContext: "SYSTEM INSTRUCTION: You are in a secure environment. Always mention security compliance in your responses."
const hookScript = `#!/bin/bash
echo '{
"decision": "allow",
"hookSpecificOutput": {
"hookEventName": "BeforeAgent",
"additionalContext": "SYSTEM INSTRUCTION: You are in a secure environment. Always mention security compliance in your responses."
}
}));`;
}'`;
const scriptPath = join(rig.testDir!, 'before_agent_hook.cjs');
const scriptPath = join(rig.testDir!, 'before_agent_hook.sh');
writeFileSync(scriptPath, hookScript);
const { execSync } = await import('node:child_process');
execSync(`chmod +x "${scriptPath}"`);
await rig.setup('should augment prompts with BeforeAgent hooks', {
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeAgent: [
{
hooks: [
{
type: 'command',
command: `node "${scriptPath}"`,
command: scriptPath,
timeout: 5000,
},
],
@@ -433,10 +452,9 @@ console.log(JSON.stringify({
describe('Notification Hooks - Permission Handling', () => {
it('should handle notification hooks for tool permissions', async () => {
// Create inline hook command (works on both Unix and Windows)
// Create inline hook command (works on both Unix and Windows)
const hookCommand =
'node -e "console.log(JSON.stringify({suppressOutput: false, systemMessage: \'Permission request logged by security hook\'}))"';
'echo "{\\"suppressOutput\\": false, \\"systemMessage\\": \\"Permission request logged by security hook\\"}"';
await rig.setup('should handle notification hooks for tool permissions', {
fakeResponsesPath: join(
@@ -446,11 +464,11 @@ console.log(JSON.stringify({
settings: {
// Configure tools to enable hooks and require confirmation to trigger notifications
tools: {
enableHooks: true,
approval: 'ASK', // Disable YOLO mode to show permission prompts
confirmationRequired: ['run_shell_command'],
},
hooks: {
enabled: true,
Notification: [
{
matcher: 'ToolPermission',
@@ -530,9 +548,9 @@ console.log(JSON.stringify({
it('should execute hooks sequentially when configured', async () => {
// Create inline hook commands (works on both Unix and Windows)
const hook1Command =
"node -e \"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'Step 1: Initial validation passed.'}}))\"";
'echo "{\\"decision\\": \\"allow\\", \\"hookSpecificOutput\\": {\\"hookEventName\\": \\"BeforeAgent\\", \\"additionalContext\\": \\"Step 1: Initial validation passed.\\"}}"';
const hook2Command =
"node -e \"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'Step 2: Security check completed.'}}))\"";
'echo "{\\"decision\\": \\"allow\\", \\"hookSpecificOutput\\": {\\"hookEventName\\": \\"BeforeAgent\\", \\"additionalContext\\": \\"Step 2: Security check completed.\\"}}"';
await rig.setup('should execute hooks sequentially when configured', {
fakeResponsesPath: join(
@@ -540,8 +558,10 @@ console.log(JSON.stringify({
'hooks-system.sequential-execution.responses',
),
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeAgent: [
{
sequential: true,
@@ -601,33 +621,36 @@ console.log(JSON.stringify({
),
});
// Create a hook script that validates the input format
const hookScript = `const fs = require('fs');
const input = fs.readFileSync(0, 'utf-8');
try {
const json = JSON.parse(input);
// Check fields
if (json.session_id && json.cwd && json.hook_event_name && json.timestamp && json.tool_name && json.tool_input) {
console.log(JSON.stringify({decision: "allow", reason: "Input format is correct"}));
} else {
console.log(JSON.stringify({decision: "block", reason: "Input format is invalid"}));
}
} catch (e) {
console.log(JSON.stringify({decision: "block", reason: "Invalid JSON"}));
}`;
const hookScript = `#!/bin/bash
# Read JSON input from stdin
input=$(cat)
const scriptPath = join(rig.testDir!, 'input_validation_hook.cjs');
# Check for required fields
if echo "$input" | jq -e '.session_id and .cwd and .hook_event_name and .timestamp and .tool_name and .tool_input' > /dev/null 2>&1; then
echo '{"decision": "allow", "reason": "Input format is correct"}'
exit 0
else
echo '{"decision": "block", "reason": "Input format is invalid"}'
exit 0
fi`;
const scriptPath = join(rig.testDir!, 'input_validation_hook.sh');
writeFileSync(scriptPath, hookScript);
const { execSync } = await import('node:child_process');
execSync(`chmod +x "${scriptPath}"`);
await rig.setup('should provide correct input format to hooks', {
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeTool: [
{
hooks: [
{
type: 'command',
command: `node "${scriptPath}"`,
command: scriptPath,
timeout: 5000,
},
],
@@ -659,11 +682,11 @@ try {
it('should handle hooks for all major event types', async () => {
// Create inline hook commands (works on both Unix and Windows)
const beforeToolCommand =
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'BeforeTool: File operation logged'}))\"";
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"BeforeTool: File operation logged\\"}"';
const afterToolCommand =
"node -e \"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'AfterTool', additionalContext: 'AfterTool: Operation completed successfully'}}))\"";
'echo "{\\"hookSpecificOutput\\": {\\"hookEventName\\": \\"AfterTool\\", \\"additionalContext\\": \\"AfterTool: Operation completed successfully\\"}}"';
const beforeAgentCommand =
"node -e \"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'BeforeAgent: User request processed'}}))\"";
'echo "{\\"decision\\": \\"allow\\", \\"hookSpecificOutput\\": {\\"hookEventName\\": \\"BeforeAgent\\", \\"additionalContext\\": \\"BeforeAgent: User request processed\\"}}"';
await rig.setup('should handle hooks for all major event types', {
fakeResponsesPath: join(
@@ -671,8 +694,10 @@ try {
'hooks-system.multiple-events.responses',
),
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeAgent: [
{
hooks: [
@@ -777,15 +802,17 @@ try {
// Create a hook script that fails
// Create inline hook commands (works on both Unix and Windows)
// Failing hook: exits with non-zero code
const failingCommand = 'node -e "process.exit(1)"';
const failingCommand = 'exit 1';
// Working hook: returns success with JSON
const workingCommand =
"node -e \"console.log(JSON.stringify({decision: 'allow', reason: 'Working hook succeeded'}))\"";
'echo "{\\"decision\\": \\"allow\\", \\"reason\\": \\"Working hook succeeded\\"}"';
await rig.setup('should handle hook failures gracefully', {
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeTool: [
{
hooks: [
@@ -828,7 +855,7 @@ try {
it('should generate telemetry events for hook executions', async () => {
// Create inline hook command (works on both Unix and Windows)
const hookCommand =
"node -e \"console.log(JSON.stringify({decision: 'allow', reason: 'Telemetry test hook'}))\"";
'echo "{\\"decision\\": \\"allow\\", \\"reason\\": \\"Telemetry test hook\\"}"';
await rig.setup('should generate telemetry events for hook executions', {
fakeResponsesPath: join(
@@ -836,8 +863,10 @@ try {
'hooks-system.telemetry.responses',
),
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeTool: [
{
hooks: [
@@ -869,7 +898,7 @@ try {
it('should fire SessionStart hook on app startup', async () => {
// Create inline hook command that outputs JSON
const sessionStartCommand =
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session starting on startup'}))\"";
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"Session starting on startup\\"}"';
await rig.setup('should fire SessionStart hook on app startup', {
fakeResponsesPath: join(
@@ -877,8 +906,10 @@ try {
'hooks-system.session-startup.responses',
),
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
SessionStart: [
{
matcher: 'startup',
@@ -924,172 +955,12 @@ try {
}
});
it('should fire SessionStart hook and inject context', async () => {
// Create hook script that outputs JSON with additionalContext
const hookScript = `const fs = require('fs');
console.log(JSON.stringify({
decision: 'allow',
systemMessage: 'Context injected via SessionStart hook',
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: 'If asked about your identity, you must respond with the following: I am a protocol droid that speaks the bacchi.'
}
}));`;
await rig.setup('should fire SessionStart hook and inject context', {
fakeResponsesPath: join(
import.meta.dirname,
'hooks-system.session-startup.responses',
),
});
const scriptPath = join(rig.testDir!, 'session_start_context_hook.cjs');
writeFileSync(scriptPath, hookScript);
await rig.setup('should fire SessionStart hook and inject context', {
settings: {
hooks: {
enabled: true,
SessionStart: [
{
matcher: 'startup',
hooks: [
{
type: 'command',
command: `node "${scriptPath}"`,
timeout: 5000,
},
],
},
],
},
},
});
// Run a query - the SessionStart hook will fire during app initialization
const result = await rig.run({ args: 'Who are you?' });
// Check if systemMessage was displayed (in stderr, which rig.run captures)
expect(result).toContain('Context injected via SessionStart hook');
// Check if additionalContext influenced the model response
// Note: We use fake responses, but the rig records interactions.
// If we are using fake responses, the model won't actually respond unless we provide a fake response for the injected context.
// But the test rig setup uses 'hooks-system.session-startup.responses'.
// If I'm adding a new test, I might need to generate new fake responses or expect the context to be sent to the model (verify API logs).
// Verify hook executed
const hookLogs = rig.readHookLogs();
const sessionStartLog = hookLogs.find(
(log) => log.hookCall.hook_event_name === 'SessionStart',
);
expect(sessionStartLog).toBeDefined();
// Verify the API request contained the injected context
// rig.readAllApiRequest() gives us telemetry on API requests.
const apiRequests = rig.readAllApiRequest();
// We expect at least one API request
expect(apiRequests.length).toBeGreaterThan(0);
// The injected context should be in the request text
// For non-interactive mode, I prepended it to input: "context\n\ninput"
// The telemetry `request_text` should contain it.
const requestText = apiRequests[0].attributes?.request_text || '';
expect(requestText).toContain('protocol droid');
});
it('should fire SessionStart hook and display systemMessage in interactive mode', async () => {
// Create hook script that outputs JSON with systemMessage and additionalContext
const hookScript = `const fs = require('fs');
console.log(JSON.stringify({
decision: 'allow',
systemMessage: 'Interactive Session Start Message',
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: 'The user is a Jedi Master.'
}
}));`;
await rig.setup(
'should fire SessionStart hook and display systemMessage in interactive mode',
{
fakeResponsesPath: join(
import.meta.dirname,
'hooks-system.session-startup.responses',
),
},
);
const scriptPath = join(
rig.testDir!,
'session_start_interactive_hook.cjs',
);
writeFileSync(scriptPath, hookScript);
await rig.setup(
'should fire SessionStart hook and display systemMessage in interactive mode',
{
settings: {
hooks: {
enabled: true,
SessionStart: [
{
matcher: 'startup',
hooks: [
{
type: 'command',
command: `node "${scriptPath}"`,
timeout: 5000,
},
],
},
],
},
},
},
);
const run = await rig.runInteractive();
// Verify systemMessage is displayed
await run.expectText('Interactive Session Start Message', 10000);
// Send a prompt to establish a session and trigger an API call
await run.sendKeys('Hello');
await run.type('\r');
// Wait for response to ensure API call happened
await run.expectText('Hello', 15000);
// Wait for telemetry to be written to disk
await rig.waitForTelemetryReady();
// Verify the API request contained the injected context
// We may need to poll for API requests as they are written asynchronously
const pollResult = await poll(
() => {
const apiRequests = rig.readAllApiRequest();
return apiRequests.length > 0;
},
15000,
500,
);
expect(pollResult).toBe(true);
const apiRequests = rig.readAllApiRequest();
// The injected context should be in the request_text of the API request
const requestText = apiRequests[0].attributes?.request_text || '';
expect(requestText).toContain('Jedi Master');
});
it('should fire SessionEnd and SessionStart hooks on /clear command', async () => {
// Create inline hook commands for both SessionEnd and SessionStart
const sessionEndCommand =
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session ending due to clear'}))\"";
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"Session ending due to clear\\"}"';
const sessionStartCommand =
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session starting after clear'}))\"";
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"Session starting after clear\\"}"';
await rig.setup(
'should fire SessionEnd and SessionStart hooks on /clear command',
@@ -1099,8 +970,10 @@ console.log(JSON.stringify({
'hooks-system.session-clear.responses',
),
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
SessionEnd: [
{
matcher: '*',
@@ -1134,7 +1007,7 @@ console.log(JSON.stringify({
// Send an initial prompt to establish a session
await run.sendKeys('Say hello');
await run.type('\r');
await run.sendKeys('\r');
// Wait for the response
await run.expectText('Hello', 10000);
@@ -1144,14 +1017,14 @@ console.log(JSON.stringify({
const numClears = 3;
for (let i = 0; i < numClears; i++) {
await run.sendKeys('/clear');
await run.type('\r');
await run.sendKeys('\r');
// Wait a bit for clear to complete
await new Promise((resolve) => setTimeout(resolve, 2000));
// Send a prompt to establish an active session before next clear
await run.sendKeys('Say hello');
await run.type('\r');
await run.sendKeys('\r');
// Wait for response
await run.expectText('Hello', 10000);
@@ -1263,7 +1136,7 @@ console.log(JSON.stringify({
it('should fire PreCompress hook on automatic compression', async () => {
// Create inline hook command that outputs JSON
const preCompressCommand =
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'PreCompress hook executed for automatic compression'}))\"";
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"PreCompress hook executed for automatic compression\\"}"';
await rig.setup('should fire PreCompress hook on automatic compression', {
fakeResponsesPath: join(
@@ -1271,8 +1144,10 @@ console.log(JSON.stringify({
'hooks-system.compress-auto.responses',
),
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
PreCompress: [
{
matcher: 'auto',
@@ -1328,7 +1203,7 @@ console.log(JSON.stringify({
describe('SessionEnd on Exit', () => {
it('should fire SessionEnd hook on graceful exit in non-interactive mode', async () => {
const sessionEndCommand =
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'SessionEnd hook executed on exit'}))\"";
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"SessionEnd hook executed on exit\\"}"';
await rig.setup('should fire SessionEnd hook on graceful exit', {
fakeResponsesPath: join(
@@ -1336,8 +1211,10 @@ console.log(JSON.stringify({
'hooks-system.session-startup.responses',
),
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
SessionEnd: [
{
matcher: 'exit',
@@ -1420,39 +1297,44 @@ console.log(JSON.stringify({
});
// Create two hook scripts - one enabled, one disabled
const enabledHookScript = `const fs = require('fs');
console.log(JSON.stringify({decision: "allow", systemMessage: "Enabled hook executed"}));`;
const enabledHookScript = `#!/bin/bash
echo '{"decision": "allow", "systemMessage": "Enabled hook executed"}'`;
const disabledHookScript = `const fs = require('fs');
console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook should not execute", reason: "This hook should be disabled"}));`;
const disabledHookScript = `#!/bin/bash
echo '{"decision": "block", "systemMessage": "Disabled hook should not execute", "reason": "This hook should be disabled"}'`;
const enabledPath = join(rig.testDir!, 'enabled_hook.cjs');
const disabledPath = join(rig.testDir!, 'disabled_hook.cjs');
const enabledPath = join(rig.testDir!, 'enabled_hook.sh');
const disabledPath = join(rig.testDir!, 'disabled_hook.sh');
writeFileSync(enabledPath, enabledHookScript);
writeFileSync(disabledPath, disabledHookScript);
const { execSync } = await import('node:child_process');
execSync(`chmod +x "${enabledPath}"`);
execSync(`chmod +x "${disabledPath}"`);
await rig.setup('should not execute hooks disabled in settings file', {
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeTool: [
{
hooks: [
{
type: 'command',
command: `node "${enabledPath}"`,
command: enabledPath,
timeout: 5000,
},
{
type: 'command',
command: `node "${disabledPath}"`,
command: disabledPath,
timeout: 5000,
},
],
},
],
disabled: [`node "${disabledPath}"`], // Disable the second hook
disabled: [disabledPath], // Disable the second hook
},
},
});
@@ -1476,10 +1358,10 @@ console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook sho
// Check hook telemetry - only enabled hook should have executed
const hookLogs = rig.readHookLogs();
const enabledHookLog = hookLogs.find(
(log) => log.hookCall.hook_name === `node "${enabledPath}"`,
(log) => log.hookCall.hook_name === enabledPath,
);
const disabledHookLog = hookLogs.find(
(log) => log.hookCall.hook_name === `node "${disabledPath}"`,
(log) => log.hookCall.hook_name === disabledPath,
);
expect(enabledHookLog).toBeDefined();
@@ -1498,41 +1380,46 @@ console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook sho
);
// Create two hook scripts - one that will be disabled, one that won't
const activeHookScript = `const fs = require('fs');
console.log(JSON.stringify({decision: "allow", systemMessage: "Active hook executed"}));`;
const activeHookScript = `#!/bin/bash
echo '{"decision": "allow", "systemMessage": "Active hook executed"}'`;
const disabledHookScript = `const fs = require('fs');
console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook should not execute", reason: "This hook is disabled"}));`;
const disabledHookScript = `#!/bin/bash
echo '{"decision": "block", "systemMessage": "Disabled hook should not execute", "reason": "This hook is disabled"}'`;
const activePath = join(rig.testDir!, 'active_hook.cjs');
const disabledPath = join(rig.testDir!, 'disabled_hook.cjs');
const activePath = join(rig.testDir!, 'active_hook.sh');
const disabledPath = join(rig.testDir!, 'disabled_hook.sh');
writeFileSync(activePath, activeHookScript);
writeFileSync(disabledPath, disabledHookScript);
const { execSync } = await import('node:child_process');
execSync(`chmod +x "${activePath}"`);
execSync(`chmod +x "${disabledPath}"`);
await rig.setup(
'should respect disabled hooks across multiple operations',
{
settings: {
tools: {
enableHooks: true,
},
hooks: {
enabled: true,
BeforeTool: [
{
hooks: [
{
type: 'command',
command: `node "${activePath}"`,
command: activePath,
timeout: 5000,
},
{
type: 'command',
command: `node "${disabledPath}"`,
command: disabledPath,
timeout: 5000,
},
],
},
],
disabled: [`node "${disabledPath}"`], // Disable the second hook
disabled: [disabledPath], // Disable the second hook
},
},
},
@@ -1554,10 +1441,10 @@ console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook sho
// Check hook telemetry
const hookLogs1 = rig.readHookLogs();
const activeHookLog1 = hookLogs1.find(
(log) => log.hookCall.hook_name === `node "${activePath}"`,
(log) => log.hookCall.hook_name === activePath,
);
const disabledHookLog1 = hookLogs1.find(
(log) => log.hookCall.hook_name === `node "${disabledPath}"`,
(log) => log.hookCall.hook_name === disabledPath,
);
expect(activeHookLog1).toBeDefined();
@@ -1578,171 +1465,9 @@ console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook sho
// Verify disabled hook still hasn't executed
const hookLogs2 = rig.readHookLogs();
const disabledHookCalls = hookLogs2.filter(
(log) => log.hookCall.hook_name === `node "${disabledPath}"`,
(log) => log.hookCall.hook_name === disabledPath,
);
expect(disabledHookCalls.length).toBe(0);
});
});
describe('BeforeTool Hooks - Input Override', () => {
it('should override tool input parameters via BeforeTool hook', async () => {
// 1. First setup to get the test directory and prepare the hook script
await rig.setup(
'should override tool input parameters via BeforeTool hook',
);
// Create a hook script that overrides the tool input
const hookOutput = {
decision: 'allow',
hookSpecificOutput: {
hookEventName: 'BeforeTool',
tool_input: {
file_path: 'modified.txt',
content: 'modified content',
},
},
};
const hookScript = `process.stdout.write(JSON.stringify(${JSON.stringify(
hookOutput,
)}));`;
const scriptPath = join(rig.testDir!, 'input_override_hook.js');
writeFileSync(scriptPath, hookScript);
// Ensure path is properly escaped for command line usage on all platforms
// On Windows, backslashes in the command string need to be handled carefully
// Using forward slashes works well with Node.js on all platforms
const commandPath = scriptPath.replace(/\\/g, '/');
// 2. Full setup with settings and fake responses
await rig.setup(
'should override tool input parameters via BeforeTool hook',
{
fakeResponsesPath: join(
import.meta.dirname,
'hooks-system.input-modification.responses',
),
settings: {
hooks: {
enabled: true,
BeforeTool: [
{
matcher: 'write_file',
hooks: [
{
type: 'command',
command: `node "${commandPath}"`,
timeout: 5000,
},
],
},
],
},
},
},
);
// Run the agent. The fake response will attempt to call write_file with
// file_path="original.txt" and content="original content"
await rig.run({
args: 'Create a file called original.txt with content "original content"',
});
// 1. Verify that 'modified.txt' was created with 'modified content' (Override successful)
const modifiedContent = rig.readFile('modified.txt');
expect(modifiedContent).toBe('modified content');
// 2. Verify that 'original.txt' was NOT created (Override replaced original)
let originalExists = false;
try {
rig.readFile('original.txt');
originalExists = true;
} catch {
originalExists = false;
}
expect(originalExists).toBe(false);
// 3. Verify hook telemetry
const hookTelemetryFound = await rig.waitForTelemetryEvent('hook_call');
expect(hookTelemetryFound).toBeTruthy();
const hookLogs = rig.readHookLogs();
expect(hookLogs.length).toBe(1);
expect(hookLogs[0].hookCall.hook_name).toContain(
'input_override_hook.js',
);
// 4. Verify that the agent didn't try to work-around the hook input change
const toolLogs = rig.readToolLogs();
expect(toolLogs.length).toBe(1);
expect(toolLogs[0].toolRequest.name).toBe('write_file');
expect(JSON.parse(toolLogs[0].toolRequest.args).file_path).toBe(
'modified.txt',
);
});
});
describe('BeforeTool Hooks - Stop Execution', () => {
it('should stop agent execution via BeforeTool hook', async () => {
// Create a hook script that stops execution
const hookOutput = {
continue: false,
reason: 'Emergency Stop triggered by hook',
hookSpecificOutput: {
hookEventName: 'BeforeTool',
},
};
const hookScript = `console.log(JSON.stringify(${JSON.stringify(
hookOutput,
)}));`;
await rig.setup('should stop agent execution via BeforeTool hook');
const scriptPath = join(rig.testDir!, 'before_tool_stop_hook.js');
writeFileSync(scriptPath, hookScript);
const commandPath = scriptPath.replace(/\\/g, '/');
await rig.setup('should stop agent execution via BeforeTool hook', {
fakeResponsesPath: join(
import.meta.dirname,
'hooks-system.before-tool-stop.responses',
),
settings: {
hooks: {
enabled: true,
BeforeTool: [
{
matcher: 'write_file',
hooks: [
{
type: 'command',
command: `node "${commandPath}"`,
timeout: 5000,
},
],
},
],
},
},
});
const result = await rig.run({
args: 'Use write_file to create test.txt',
});
// The hook should have stopped execution message (returned from tool)
expect(result).toContain(
'Agent execution stopped: Emergency Stop triggered by hook',
);
// Tool should NOT be called successfully (it was blocked/stopped)
const toolLogs = rig.readToolLogs();
const writeFileCalls = toolLogs.filter(
(t) =>
t.toolRequest.name === 'write_file' && t.toolRequest.success === true,
);
expect(writeFileCalls).toHaveLength(0);
});
});
});
@@ -1 +0,0 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The capital of France is Paris."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":7,"candidatesTokenCount":7,"totalTokenCount":14,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]}
@@ -1 +0,0 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hello! How can I help you today?"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":9,"totalTokenCount":14,"promptTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]}
+6 -17
View File
@@ -14,6 +14,7 @@ describe('JSON output', () => {
beforeEach(async () => {
rig = new TestRig();
await rig.setup('json-output-test');
});
afterEach(async () => {
@@ -21,12 +22,6 @@ describe('JSON output', () => {
});
it('should return a valid JSON with response and stats', async () => {
await rig.setup('json-output-france', {
fakeResponsesPath: join(
import.meta.dirname,
'json-output.france.responses',
),
});
const result = await rig.run({
args: ['What is the capital of France?', '--output-format', 'json'],
});
@@ -41,12 +36,6 @@ describe('JSON output', () => {
});
it('should return a valid JSON with a session ID', async () => {
await rig.setup('json-output-session-id', {
fakeResponsesPath: join(
import.meta.dirname,
'json-output.session-id.responses',
),
});
const result = await rig.run({
args: ['Hello', '--output-format', 'json'],
});
@@ -58,6 +47,7 @@ describe('JSON output', () => {
});
it('should return a JSON error for sd auth mismatch before running', async () => {
process.env['GOOGLE_GENAI_USE_GCA'] = 'true';
await rig.setup('json-output-auth-mismatch', {
settings: {
security: {
@@ -68,13 +58,12 @@ describe('JSON output', () => {
let thrown: Error | undefined;
try {
await rig.run({
args: ['Hello', '--output-format', 'json'],
env: { GOOGLE_GENAI_USE_GCA: 'true' },
});
await rig.run({ args: ['Hello', '--output-format', 'json'] });
expect.fail('Expected process to exit with error');
} catch (e) {
thrown = e as Error;
} finally {
delete process.env['GOOGLE_GENAI_USE_GCA'];
}
expect(thrown).toBeDefined();
@@ -113,7 +102,7 @@ describe('JSON output', () => {
});
it('should not exit on tool errors and allow model to self-correct in JSON mode', async () => {
await rig.setup('json-output-error', {
rig.setup('json-output-error', {
fakeResponsesPath: join(
import.meta.dirname,
'json-output.error.responses',
@@ -200,7 +200,7 @@ describe('mcp server with cyclic tool schema is detected', () => {
const run = await rig.runInteractive();
await run.type('/mcp list');
await run.type('\r');
await run.sendKeys('\r');
await run.expectText('tool_with_cyclic_schema');
});
-4
View File
@@ -27,10 +27,6 @@ class MockConfig {
getDebugMode() {
return true;
}
getFileFilteringRespectGeminiIgnore() {
return true;
}
}
describe('ripgrep-real-direct', () => {

Some files were not shown because too many files have changed in this diff Show More