Skip to content

fix(scheduler): enforce user hold directives by blocking mutating tools at scheduler layer - #29394

Closed
dylanyunlon wants to merge 1 commit into
google-gemini:mainfrom
dylanyunlon:fix/hold-directive-enforcement-26390
Closed

dylanyunlon wants to merge 1 commit into
google-gemini:mainfrom
dylanyunlon:fix/hold-directive-enforcement-26390

Conversation

@dylanyunlon

Copy link
Copy Markdown

Summary

Resolves #26390

The agent exhibits an aggressive action-bias that overrides explicit user hold directives. When users say "wait", "explain first", or "do not apply fixes yet", the agent still fires destructive tool calls (replace, write_file, run_shell_command). Prompt-level instructions alone are insufficient, the model RLHF-driven completion bias overwhelms the attention weights on negative constraints.

This fix adds programmatic enforcement at the scheduler layer, blocking mutating tool calls before they reach the policy/confirmation/execution pipeline.

Details

New: User Directive Detection Service (userDirectiveService.ts)

  • Detects hold/wait/explain directives from user messages using 14 regex patterns across 4 directive types: EXPLICIT_WAIT, EXPLAIN_ONLY, NO_CHANGES_YET, REVIEW_FIRST
  • Includes negation guards to avoid false positives on "do not wait" / "go ahead" / "proceed"
  • Classifies directives with confidence scores (0.8-0.95)

New: Hold Directive Guard (holdDirectiveGuard.ts)

  • Checks tool calls against the active hold directive at the scheduler layer
  • Only mutating tools (write_file, replace, run_shell_command, write_todos, invoke_agent) are blocked
  • Read-only tools (read_file, grep, glob, ls, web_search) remain available for continued investigation

Modified: Scheduler Integration (scheduler.ts)

  • Hold directive guard runs in _startBatch() before tool validation
  • Blocked calls get immediate error with structured [HOLD DIRECTIVE ACTIVE] message
  • Added setActiveHoldDirective() for runtime directive updates

Modified: CLI Integration (nonInteractiveCli.ts, nonInteractiveCliAgentSession.ts)

  • Both headless execution paths detect hold directives from user input
  • Scheduler is configured before the agent loop starts

Modified: System Prompt (snippets.ts)

  • "Expertise & Intent Alignment" section now documents the programmatic enforcement
  • Model is told that mutating tools will be system-blocked during hold directives

New Error Type (tool-error.ts)

  • HOLD_DIRECTIVE_VIOLATION for precise error categorization

Context Extension (agent-loop-context.ts, agent-scheduler.ts)

  • activeHoldDirective field propagated through the execution context

Test Coverage

How to Validate

npm test -w @google/gemini-cli-core -- --run src/services/userDirectiveService.test.ts
npm test -w @google/gemini-cli-core -- --run src/scheduler/holdDirectiveGuard.test.ts
npm test -w @google/gemini-cli-core -- --run src/services/holdDirectiveIntegration.test.ts

Manual validation with mock API:

GOOGLE_GEMINI_BASE_URL="http://localhost:19876" \
GEMINI_API_KEY="test" \
gemini -p "find bugs but do not apply fixes yet" --yolo --skip-trust

Expected: model replace call returns [HOLD DIRECTIVE ACTIVE] error, file unchanged.

Pre-Merge Checklist

  • Code compiles (tsc --noEmit passes)
  • All new tests pass (77/77)
  • No regressions in scheduler test suite (169/169)
  • Lint + Prettier pass
  • E2E validated with real CLI binary against mock API

Related Issues

Resolves #26390

@dylanyunlon
dylanyunlon requested review from a team as code owners September 18, 2026 10:41
@google-cla

google-cla Bot commented Sep 18, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@github-actions github-actions Bot added the size/xl An extra large PR label Sep 18, 2026
@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

📊 PR Size: size/XL

  • Lines changed: 1371
  • Additions: +1370
  • Deletions: -1
  • Files changed: 14

@dylanyunlon

Copy link
Copy Markdown
Author

@gemini-code-assist Please review this PR.

This addresses issue #26390 (action-bias overriding explicit user hold directives). The core change adds programmatic enforcement at the scheduler layer so that when a user says "don't apply yet" or "explain first", mutating tool calls are blocked before they reach the execution pipeline.

Key areas for review:

  1. Regex pattern coverage in userDirectiveService.ts, particularly the negation guards that prevent false positives on "go ahead" / "proceed"
  2. Scheduler integration in scheduler.ts _startBatch(), the hold directive check runs before tool validation
  3. CLI integration in both nonInteractiveCli.ts (legacy path) and nonInteractiveCliAgentSession.ts (agent session path)
  4. Test coverage: 77 new tests across 3 test files

@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

🛑 Action Required: Evaluation Approval

Steering changes have been detected in this PR. To prevent regressions, a maintainer must approve the evaluation run before this PR can be merged.

Maintainers:

  1. Go to the Workflow Run Summary.
  2. Click the yellow 'Review deployments' button.
  3. Select the 'eval-gate' environment and click 'Approve'.

Once approved, the evaluation results will be posted here automatically.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a programmatic mechanism to enforce user hold directives, preventing the agent from executing mutating tool calls when a user explicitly requests to wait, explain first, or review before applying changes. By intercepting these calls at the scheduler layer, the system ensures that negative constraints are respected even when the model's internal bias might otherwise lead to premature actions.

Highlights

  • Directive Detection Service: Added userDirectiveService.ts to identify user hold directives using regex patterns and confidence scoring.
  • Scheduler Enforcement: Implemented holdDirectiveGuard.ts and integrated it into the scheduler to block mutating tool calls when a hold directive is active.
  • System Prompt Update: Updated system instructions to explicitly inform the model about the programmatic enforcement of hold directives.
  • Test Coverage: Added 48 unit tests for detection, 20 for the guard, and 9 integration tests to ensure end-to-end functionality.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a programmatic Hold Directive Guard mechanism to enforce user hold directives (such as "don't apply yet" or "explain first") by blocking mutating tool calls at the scheduler layer while keeping read-only tools available. Key feedback highlights opportunities to improve robustness and security: supporting standalone wait/hold words, refining negation patterns to prevent false negatives on phrases like "just do the analysis", adopting a secure-by-default approach in isMutatingTool by blocking any tool not explicitly in the read-only allowlist, safely handling and trimming the optional errorMessage in the scheduler, and ensuring consistent state clearing in the non-interactive CLI.

Comment on lines +45 to +49
pattern:
/\b(?:wait|hold|pause|stop|halt)\b(?:\s+(?:for|until)\s+(?:my|me|user|i\s|further)|\s+(?:before|on|here|there|now|first)|\s*[,.]?\s*(?:don'?t|do\s+not|let me))/i,
type: HoldDirectiveType.EXPLICIT_WAIT,
confidence: 0.95,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Standalone directives like "wait", "hold", or "stop" are the most direct way a user might instruct the agent to pause. However, the current regex pattern for EXPLICIT_WAIT requires trailing context words (e.g., "wait for me", "hold on"), meaning a single-word message like "wait" or "hold" will be completely ignored.

Adding a pattern specifically for standalone wait/hold/pause/stop/halt words ensures these direct user commands are programmatically enforced.

  {
    pattern:
      /\b(?:wait|hold|pause|stop|halt)\b(?:\s+(?:for|until)\s+(?:my|me|user|i\s|further)|\s+(?:before|on|here|there|now|first)|\s*[,.]?\s*(?:don'?t|do\s+not|let me))/i,
    type: HoldDirectiveType.EXPLICIT_WAIT,
    confidence: 0.95,
  },
  {
    pattern: /^(?:wait|hold|pause|stop|halt)[.,!?]*$/i,
    type: HoldDirectiveType.EXPLICIT_WAIT,
    confidence: 0.95,
  },

Comment on lines +119 to +124
const NEGATION_PATTERNS: RegExp[] = [
/\b(?:don'?t|do\s+not)\s+(?:wait|hold|pause|stop)\b/i,
/\bjust\s+(?:do|apply|fix|implement|make|go ahead|proceed)\b/i,
/\bgo\s+ahead\b/i,
/\bproceed\b/i,
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The negation pattern \bjust\s+(?:do|apply|fix|implement|make|go ahead|proceed)\b is overly broad because of the do keyword. If a user says "just do the analysis, don't apply yet", the phrase "just do" will trigger the negation pattern and completely disable the hold directive detection. This allows the agent to bypass the hold and aggressively mutate the codebase.

Refining the pattern to require "it" or other explicit action targets for "do" (e.g., just do it) prevents these false negatives.

Suggested change
const NEGATION_PATTERNS: RegExp[] = [
/\b(?:don'?t|do\s+not)\s+(?:wait|hold|pause|stop)\b/i,
/\bjust\s+(?:do|apply|fix|implement|make|go ahead|proceed)\b/i,
/\bgo\s+ahead\b/i,
/\bproceed\b/i,
];
const NEGATION_PATTERNS: RegExp[] = [
/\b(?:don'?t|do\s+not)\s+(?:wait|hold|pause|stop)\b/i,
/\bjust\s+(?:apply|fix|implement|make|go ahead|proceed)\b|\bjust\s+do\s+it\b/i,
/\bgo\s+ahead\b/i,
/\bproceed\b/i,
];

Comment on lines +184 to +187
export function isMutatingTool(toolName: string): boolean {
// Also treat invoke_agent as mutating since agents can perform writes.
return MUTATING_TOOL_NAMES.has(toolName) || toolName === 'invoke_agent';
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of isMutatingTool relies on checking a hardcoded set of mutating tools (MUTATING_TOOL_NAMES). This approach is fragile and insecure by default:

  1. Any custom or third-party MCP tools that perform mutations (e.g., mcp_filesystem_write_file) will bypass the hold directive guard entirely.
  2. Sibling tools like tracker_create_task or tracker_update_task (which modify files in the workspace) are not blocked.

To enforce a "secure by default" methodology, we should treat any tool not explicitly listed in the read-only allowlist (READ_ONLY_TOOL_NAMES) as mutating.

Suggested change
export function isMutatingTool(toolName: string): boolean {
// Also treat invoke_agent as mutating since agents can perform writes.
return MUTATING_TOOL_NAMES.has(toolName) || toolName === 'invoke_agent';
}
export function isMutatingTool(toolName: string): boolean {
// Secure by default: any tool not explicitly in the read-only allowlist is treated as mutating.
return !READ_ONLY_TOOL_NAMES.has(toolName);
}
References
  1. When a core tools allowlist is configured, the default policy for non-allowlisted tools should be DENY to enforce a 'secure by default' methodology.

Comment on lines +360 to +368
if (holdCheck?.blocked) {
return {
status: CoreToolCallStatus.Error,
request: enrichedRequest,
response: createErrorResponse(
enrichedRequest,
new Error(holdCheck.errorMessage),
holdCheck.errorType,
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since errorMessage is defined as optional (string | undefined) in the HoldDirectiveCheckResult interface, callers must handle the undefined case. Additionally, when using an optional string with a fallback value, we should trim the optional string and use the fallback if the result is empty to avoid uninformative messages from whitespace-only strings.

Suggested change
if (holdCheck?.blocked) {
return {
status: CoreToolCallStatus.Error,
request: enrichedRequest,
response: createErrorResponse(
enrichedRequest,
new Error(holdCheck.errorMessage),
holdCheck.errorType,
),
const holdCheck = holdBlockedCalls.get(request.callId);
if (holdCheck?.blocked) {
return {
status: CoreToolCallStatus.Error,
request: enrichedRequest,
response: createErrorResponse(
enrichedRequest,
new Error(holdCheck.errorMessage?.trim() || 'Tool call blocked by active hold directive'),
holdCheck.errorType,
),
durationMs: 0,
schedulerId: this.schedulerId,
approvalMode: currentApprovalMode,
} as ErroredToolCall;
}
References
  1. When consuming an object, if a property is optional in its type definition (interface), callers must handle the undefined case (e.g., by providing a default with ??). Do not rely on the implementation details of the function that creates the object to always provide a value, as this can change. Code against the interface contract.
  2. When using an optional string with a fallback value, trim the optional string and use the fallback if the result is empty to avoid uninformative messages from whitespace-only strings.

Comment on lines +314 to +322
const holdDirective = detectHoldDirective(input);
if (holdDirective) {
scheduler.setActiveHoldDirective(holdDirective);
debugLogger.log(
`[HoldDirective] Detected "${holdDirective.type}" directive ` +
`(matched: "${holdDirective.matchedPhrase}"). ` +
`Mutating tools will be blocked for this turn.`,
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

For consistency with nonInteractiveCliAgentSession.ts and to prevent any potential stale state if the scheduler instance is ever reused or kept alive across multiple turns/sessions in other contexts, we should explicitly clear the active hold directive when none is detected.

Suggested change
const holdDirective = detectHoldDirective(input);
if (holdDirective) {
scheduler.setActiveHoldDirective(holdDirective);
debugLogger.log(
`[HoldDirective] Detected "${holdDirective.type}" directive ` +
`(matched: "${holdDirective.matchedPhrase}"). ` +
`Mutating tools will be blocked for this turn.`,
);
}
const holdDirective = detectHoldDirective(input);
if (holdDirective) {
scheduler.setActiveHoldDirective(holdDirective);
debugLogger.log(
`[HoldDirective] Detected "${holdDirective.type}" directive ` +
`(matched: "${holdDirective.matchedPhrase}"). ` +
`Mutating tools will be blocked for this turn.`,
);
} else {
scheduler.setActiveHoldDirective(null);
}

@gemini-cli gemini-cli Bot added priority/p1 Important and should be addressed in the near term. area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality labels Sep 18, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a programmatic enforcement mechanism for user hold directives (such as "don't apply yet" or "explain first") to prevent the agent from executing mutating tool calls when a hold is active. It adds detection logic in userDirectiveService, integrates a guard at the scheduler layer, updates system prompts to inform the model of this constraint, and includes comprehensive unit and integration tests. The review feedback correctly identifies a critical regex false positive in the NO_CHANGES_YET pattern that could unintentionally block mutating tools for general negative commands, and provides a robust regex suggestion to make the temporal suffix mandatory.

Comment on lines +50 to +55
{
pattern:
/\b(?:don'?t|do\s+not|never)\s+(?:apply|make|execute|run|implement|modify|change|write|edit|replace|update|fix|rewrite)\b(?:\s+(?:any|the|those|these|it|that|them))?\b(?:\s+(?:yet|now|until|before|first))?/i,
type: HoldDirectiveType.NO_CHANGES_YET,
confidence: 0.95,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Critical Regex False Positive Issue

The current regex pattern for NO_CHANGES_YET makes the temporal/conditional suffix (yet, now, until, before, first) completely optional due to the trailing ?.

This causes the regex to match any negative command containing these verbs (e.g., "do not write duplicate tests", "never run commands as root", "do not change the port"). Because the scheduler programmatically blocks all mutating tools when a hold directive is active, these false positives will completely break the agent's ability to write files or run commands, failing the user's request.

Solution

Make the temporal/conditional suffix mandatory and allow optional intermediate words using [^.]*? (to keep it within the same sentence). This ensures we only match actual hold directives while ignoring general negative constraints.

Suggested change
{
pattern:
/\b(?:don'?t|do\s+not|never)\s+(?:apply|make|execute|run|implement|modify|change|write|edit|replace|update|fix|rewrite)\b(?:\s+(?:any|the|those|these|it|that|them))?\b(?:\s+(?:yet|now|until|before|first))?/i,
type: HoldDirectiveType.NO_CHANGES_YET,
confidence: 0.95,
},
{
pattern:
/\b(?:don'?t|do\s+not|never)\s+(?:apply|make|execute|run|implement|modify|change|write|edit|replace|update|fix|rewrite)\b[^.]*?\b(?:yet|now|until|before|first)\b/i,
type: HoldDirectiveType.NO_CHANGES_YET,
confidence: 0.95,
},

@dylanyunlon
dylanyunlon force-pushed the fix/hold-directive-enforcement-26390 branch from f843155 to 1d2bc29 Compare September 18, 2026 11:02
@dylanyunlon

Copy link
Copy Markdown
Author

All 6 review findings from @gemini-code-assist have been addressed in the force-pushed update:

Review #1 (high) - Standalone wait/hold directives: Added dedicated pattern ^(?:wait|hold|pause|stop|halt)[.!]?$ for single-word messages.

Review #2 (high) - Overly broad "just do" negation: Narrowed from just\s+(?:do|apply|...) to just\s+(?:apply|fix|...) | just\s+do\s+it. Now "just do the analysis, don't apply yet" correctly detects the hold.

Review #3 (high) - Secure by default for unknown tools: Flipped isMutatingTool() from blocklist to allowlist approach: any tool NOT in READ_ONLY_TOOL_NAMES is treated as mutating. Custom MCP tools and new built-in tools are blocked until explicitly allowlisted.

Review #4 (high) - Optional errorMessage handling: Added ?.trim() || 'Tool call blocked by active user hold directive.' fallback in scheduler.

Review #5 (high) - Stale hold directive state: Added else { scheduler.setActiveHoldDirective(null) } branch in nonInteractiveCli.ts for consistency with nonInteractiveCliAgentSession.ts.

Review #6 (CRITICAL) - Regex false positives: Made temporal suffix mandatory in NO_CHANGES_YET pattern using [^.]*?\b(?:yet|now|until|before|first|for now)\b. Now "do not write duplicate tests" and "never run commands as root" correctly pass through without triggering a hold.

All 78 tests pass (77 existing + 1 new for Review #6). E2E CLI run confirmed: replace tool call blocked with [HOLD DIRECTIVE ACTIVE], source file unchanged.

@dylanyunlon

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a programmatic hold directive mechanism that blocks mutating tool calls when a user requests the agent to wait, explain, or review findings first. The implementation includes regex-based detection in userDirectiveService.ts, scheduler-level enforcement via holdDirectiveGuard.ts, and updates to CLI sessions and prompt snippets. The review feedback identifies several critical issues with the regex patterns, specifically pointing out potential false positives from crossing newlines, clause boundaries, or matching negative constraints without temporal suffixes. Additionally, the feedback notes that classifying all shell commands as mutating could incorrectly block non-destructive commands like test execution.

Comment on lines +59 to +64
{
pattern:
/\b(?:don'?t|do\s+not)\s+(?:apply|make|execute|run|implement|modify|change|write|edit|replace|update|fix|rewrite)\b[^.]*?\b(?:yet|now|until|before|first|for now)\b/i,
type: HoldDirectiveType.NO_CHANGES_YET,
confidence: 0.95,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The regex pattern uses [^.]*? to find a temporal suffix within the same sentence. However, [^.] matches newlines, meaning it can cross line breaks in multi-line chat messages. In chat interfaces, users often use newlines instead of periods to separate thoughts (e.g., "Do not write duplicate tests\nNow, let's implement the feature"). This will cause a false positive match on "Now" on the second line, incorrectly triggering a hold directive and blocking all mutating tools for the entire turn.

To prevent crossing newlines or other sentence boundaries, exclude newlines and other common sentence delimiters from the character class.

Suggested change
{
pattern:
/\b(?:don'?t|do\s+not)\s+(?:apply|make|execute|run|implement|modify|change|write|edit|replace|update|fix|rewrite)\b[^.]*?\b(?:yet|now|until|before|first|for now)\b/i,
type: HoldDirectiveType.NO_CHANGES_YET,
confidence: 0.95,
},
{
pattern:
/\b(?:don'?t|do\s+not)\s+(?:apply|make|execute|run|implement|modify|change|write|edit|replace|update|fix|rewrite)\b[^\n.!?]*?\b(?:yet|now|until|before|first|for now)\b/i,
type: HoldDirectiveType.NO_CHANGES_YET,
confidence: 0.95,
},

Comment on lines +71 to +77
{
pattern:
/\bbut\s+(?:not\s+now|wait|first|hold|don'?t\s+(?:apply|make|change|fix|modify))/i,
type: HoldDirectiveType.NO_CHANGES_YET,
confidence: 0.9,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The pattern matches but don't change or but don't make without requiring any temporal suffix (like "yet" or "now"). This causes it to trigger false positives on common negative constraints in user prompts, such as "Fix the bug in auth.ts, but don't change the existing tests" or "Implement the feature, but don't make duplicate files". When triggered, this incorrectly activates a hold directive and programmatically blocks all mutating tools, completely preventing the agent from executing the requested task.

Since the main NO_CHANGES_YET pattern already handles "don't change ... yet" with proper temporal suffixes, we should remove the suffix-less don'?t sub-pattern from this "but" pattern to avoid these false positives.

Suggested change
{
pattern:
/\bbut\s+(?:not\s+now|wait|first|hold|don'?t\s+(?:apply|make|change|fix|modify))/i,
type: HoldDirectiveType.NO_CHANGES_YET,
confidence: 0.9,
},
// "but not now", "but wait", "but first explain"
{
pattern:
/\bbut\s+(?:not\s+now|wait|first|hold)/i,
type: HoldDirectiveType.NO_CHANGES_YET,
confidence: 0.9,
},

Comment on lines +104 to +109
{
pattern:
/\b(?:show|present|give)\s+(?:me\s+)?(?:the\s+)?(?:full\s+)?(?:review|report|analysis|findings|results)\b.*\b(?:before)\b/i,
type: HoldDirectiveType.REVIEW_FIRST,
confidence: 0.85,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using the greedy wildcard .* allows matching across clause or sentence boundaries on the same line. This can lead to false positives on compound sentences where the user actually wants the agent to proceed with an action (e.g., "Show me the report. Before that, let's write the file."). In this case, the agent will match "Show me the report... before" and block the write_file tool, which is the exact action the user requested.

To prevent matching across clause or sentence boundaries, restrict the wildcard to not cross punctuation marks like periods, commas, semicolons, or question marks.

Suggested change
{
pattern:
/\b(?:show|present|give)\s+(?:me\s+)?(?:the\s+)?(?:full\s+)?(?:review|report|analysis|findings|results)\b.*\b(?:before)\b/i,
type: HoldDirectiveType.REVIEW_FIRST,
confidence: 0.85,
},
{
pattern:
/\b(?:show|present|give)\s+(?:me\s+)?(?:the\s+)?(?:full\s+)?(?:review|report|analysis|findings|results)\b[^,.;!?\n]*?\b(?:before)\b/i,
type: HoldDirectiveType.REVIEW_FIRST,
confidence: 0.85,
},

Comment on lines +116 to +121
{
pattern:
/\bi\s+will\s+(?:run|do|apply|check|review|test)\b.*\b(?:first|myself|later|after)\b/i,
type: HoldDirectiveType.REVIEW_FIRST,
confidence: 0.85,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using the greedy wildcard .* allows matching across clause or sentence boundaries on the same line. This can lead to false positives on compound sentences (e.g., "I will do it later, for now just implement the basic structure."). Here, "I will do... later" matches, triggering a hold directive and blocking all mutating tools, which prevents the agent from implementing the requested structure.

To prevent matching across clause or sentence boundaries, restrict the wildcard to not cross punctuation marks like periods, commas, semicolons, or question marks.

Suggested change
{
pattern:
/\bi\s+will\s+(?:run|do|apply|check|review|test)\b.*\b(?:first|myself|later|after)\b/i,
type: HoldDirectiveType.REVIEW_FIRST,
confidence: 0.85,
},
{
pattern:
/\bi\s+will\s+(?:run|do|apply|check|review|test)\b[^,.;!?\n]*?\b(?:first|myself|later|after)\b/i,
type: HoldDirectiveType.REVIEW_FIRST,
confidence: 0.85,
},

Comment on lines +110 to +115
{
pattern:
/\b(?:before|prior\s+to)\s+(?:making|applying|implementing|executing|running)\s+(?:any\s+)?(?:changes|fixes|modifications|updates)\b/i,
type: HoldDirectiveType.REVIEW_FIRST,
confidence: 0.9,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This pattern matches phrases like "before making any changes". If a user instructs the agent to "run the tests before making any changes", this pattern will trigger a hold directive. Because run_shell_command is classified as a mutating tool, the scheduler will block the agent from running the tests, completely defeating the user's request.

Consider refining the tool classification or the hold directive guard to allow non-destructive shell commands (like test execution or linting) to run even when a hold directive is active, or document this as a known limitation.

@dylanyunlon
dylanyunlon force-pushed the fix/hold-directive-enforcement-26390 branch from 1d2bc29 to f464440 Compare September 18, 2026 11:29
@dylanyunlon

Copy link
Copy Markdown
Author

Round 2 review findings from @gemini-code-assist addressed:

Review #7 (high) - Newline crossing: Newlines are now converted to . (period + space) during normalization, making them sentence boundaries. [^.\n] in regex stops at both periods and any residual newlines.

Review #8 (high) - "but don't change" without temporal suffix: Split into two patterns: but not now|wait|first|hold (standalone) and but don't <verb> ... yet|now|first (requires temporal suffix). "Fix the bug, but don't change the existing tests" now correctly passes through.

Review #9 (high) - Greedy .* in report-before pattern: Replaced with [^.\n]* to stay within one clause. "Show me the report. Before that, write the file." no longer triggers.

Review #10 (high) - Greedy .* in I-will pattern: Same fix, [^.\n]*. "I will do it later, for now just implement" no longer triggers.

Review #11 (high) - "run tests before making changes" false positive: Pattern now requires before/prior to at sentence start via (?:^|[.!?]\s+). "run the tests before making any changes" is correctly recognized as an action directive, not a hold.

Added 6 new tests specifically for these false-positive scenarios. All 84 tests pass. CLI run confirmed: [HOLD DIRECTIVE ACTIVE] blocks replace, source unchanged.

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a programmatic 'Hold Directive' mechanism to enforce user constraints (such as 'explain first' or 'don't apply yet') by blocking mutating tool calls at the scheduler layer while keeping read-only tools available. It adds detection logic, scheduler integration, prompt updates, and comprehensive unit and integration tests. The review feedback highlights two critical issues in userDirectiveService.ts: first, the 'i will ...' pattern matches 'later' and 'after', causing false positives that incorrectly block the agent; second, converting newlines to periods during whitespace normalization breaks multi-line directive phrases and triggers false positives on sentence-start patterns. Code suggestions are provided to restrict the pattern matching and preserve newlines during normalization.

Comment on lines +125 to +130
{
pattern:
/\bi\s+will\s+(?:run|do|apply|check|review|test)\b[^.\n]*\b(?:first|myself|later|after)\b/i,
type: HoldDirectiveType.REVIEW_FIRST,
confidence: 0.85,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The pattern matches later and after in i will ..., which causes false positives when the user says they will do something "later" or "after" (e.g., "I will review the changes later, for now implement the fix"). This incorrectly blocks the agent from acting. We should restrict this pattern to first|myself to only match immediate hold directives.

Suggested change
{
pattern:
/\bi\s+will\s+(?:run|do|apply|check|review|test)\b[^.\n]*\b(?:first|myself|later|after)\b/i,
type: HoldDirectiveType.REVIEW_FIRST,
confidence: 0.85,
},
{
pattern:
/\bi\s+will\s+(?:run|do|apply|check|review|test)\b[^.\n]*\b(?:first|myself)\b/i,
type: HoldDirectiveType.REVIEW_FIRST,
confidence: 0.85,
},

Comment on lines +160 to +167
// Normalize whitespace for matching. Newlines are treated as sentence
// boundaries (converted to periods) to prevent cross-line false positives
// (Round 2 Review #7).
const normalized = userMessage
.replace(/\r\n|\r/g, '\n')
.replace(/\n/g, '. ')
.replace(/\s+/g, ' ')
.trim();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Converting newlines (\n) to periods (. ) during normalization breaks multi-line messages where a newline falls inside a directive phrase (e.g., wait\nfor me or explain\nfirst), and causes false positives for sentence-start patterns like (?:^|[.!?]\s+) (e.g., run tests\nbefore making changes). Since the regex patterns already use [^.\n] to prevent crossing sentence and newline boundaries, and \s+ naturally matches newlines for intra-sentence wrapping, we should preserve newlines and only normalize horizontal whitespace.

Suggested change
// Normalize whitespace for matching. Newlines are treated as sentence
// boundaries (converted to periods) to prevent cross-line false positives
// (Round 2 Review #7).
const normalized = userMessage
.replace(/\r\n|\r/g, '\n')
.replace(/\n/g, '. ')
.replace(/\s+/g, ' ')
.trim();
// Normalize horizontal whitespace while preserving newlines.
// The regex patterns use [^.\n] to prevent crossing sentence and newline
// boundaries, while \s+ naturally matches newlines for intra-sentence wrapping.
const normalized = userMessage
.replace(/\r\n|\r/g, '\n')
.replace(/[ \t]+/g, ' ')
.replace(/ \n/g, '\n')
.replace(/\n /g, '\n')
.trim();

@dylanyunlon
dylanyunlon force-pushed the fix/hold-directive-enforcement-26390 branch from f464440 to fb4a3cb Compare September 18, 2026 11:35
@dylanyunlon

Copy link
Copy Markdown
Author

Round 3 review findings addressed:

Review #12 (high) - "I will ... later/after" false positive: Restricted pattern to only match first|myself. "I will review later, for now implement" no longer triggers a hold.

Review #13 (high) - Newline-to-period normalization breaks multi-line directives: Switched to preserving newlines and only normalizing horizontal whitespace ([^\S\n]+ -> " "). Regex [^.\n] boundaries now work correctly on raw newlines. Updated sentence-start patterns to also match after \n.

All 84 tests pass. CLI run verified.

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a programmatic hold directive mechanism to block mutating tool calls (such as file modifications and shell commands) when a user requests the agent to wait or explain first, while keeping read-only tools available. The implementation includes a new userDirectiveService for detection and a holdDirectiveGuard integrated into the scheduler. The review feedback suggests improving regex patterns to use [^.!?\n] instead of [^.\n] to prevent matching across sentence boundaries, addressing a potential false negative issue with global negation patterns in compound sentences, and using imported tool name constants in prompt snippets instead of hardcoding them.

Note: Security Review did not run due to the size of the PR.

// [^.\n]*? prevents crossing sentence AND newline boundaries.
{
pattern:
/\b(?:don'?t|do\s+not)\s+(?:apply|make|execute|run|implement|modify|change|write|edit|replace|update|fix|rewrite)\b[^.\n]*?\b(?:yet|now|until|before|first|for now)\b/i,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using [^.\n] only excludes literal periods and newlines, meaning other sentence/clause boundaries like ! and ? are not excluded. This allows the regex to match across sentence boundaries, leading to false positives where a hold directive in one sentence is incorrectly combined with an action directive in the next sentence (e.g., 'do not apply! Now, let's write the file.'). Excluding ! and ? by using [^.!?\n] instead of [^.\n] prevents these false positives.

Suggested change
/\b(?:don'?t|do\s+not)\s+(?:apply|make|execute|run|implement|modify|change|write|edit|replace|update|fix|rewrite)\b[^.\n]*?\b(?:yet|now|until|before|first|for now)\b/i,
/\b(?:don'?t|do\s+not)\s+(?:apply|make|execute|run|implement|modify|change|write|edit|replace|update|fix|rewrite)\b[^.!?\n]*?\b(?:yet|now|until|before|first|for now)\b/i,

},
{
pattern:
/\bbut\s+don'?t\s+(?:apply|make|change|fix|modify)\b[^.\n]*?\b(?:yet|now|first|for now)\b/i,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the previous pattern, using [^.!?\n] instead of [^.\n] ensures that sentence boundaries like ! and ? are respected, preventing false positives on compound sentences.

Suggested change
/\bbut\s+don'?t\s+(?:apply|make|change|fix|modify)\b[^.\n]*?\b(?:yet|now|first|for now)\b/i,
/\bbut\s+don'?t\s+(?:apply|make|change|fix|modify)\b[^.!?\n]*?\b(?:yet|now|first|for now)\b/i,

// Round 2 Review #9: replaced greedy .* with [^.\n]* to stay within one clause.
{
pattern:
/\b(?:show|present|give)\s+(?:me\s+)?(?:the\s+)?(?:full\s+)?(?:review|report|analysis|findings|results)\b[^.\n]*\b(?:before)\b/i,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using [^.!?\n] instead of [^.\n] prevents matching across sentence boundaries marked by ! or ?.

Suggested change
/\b(?:show|present|give)\s+(?:me\s+)?(?:the\s+)?(?:full\s+)?(?:review|report|analysis|findings|results)\b[^.\n]*\b(?:before)\b/i,
/\b(?:show|present|give)\s+(?:me\s+)?(?:the\s+)?(?:full\s+)?(?:review|report|analysis|findings|results)\b[^.!?\n]*\b(?:before)\b/i,

// caused false positives on "I will review later, for now implement".
{
pattern:
/\bi\s+will\s+(?:run|do|apply|check|review|test)\b[^.\n]*\b(?:first|myself)\b/i,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using [^.!?\n] instead of [^.\n] prevents matching across sentence boundaries marked by ! or ?.

Suggested change
/\bi\s+will\s+(?:run|do|apply|check|review|test)\b[^.\n]*\b(?:first|myself)\b/i,
/\bi\s+will\s+(?:run|do|apply|check|review|test)\b[^.!?\n]*\b(?:first|myself)\b/i,

Comment on lines +176 to +180
for (const negPattern of NEGATION_PATTERNS) {
if (negPattern.test(normalized)) {
return null;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Checking negation patterns globally across the entire user message causes false negatives (failing to block mutating tools) when a user combines a permissive phrase with a negative constraint in a compound sentence (e.g., 'go ahead with the research, but do not modify files yet' or 'proceed with the investigation, but hold on before fixing'). Because 'go ahead' or 'proceed' matches globally, the entire hold directive is bypassed. Consider evaluating negation patterns on a per-clause basis or ensuring that explicit hold patterns take precedence over global negations.

Comment thread packages/core/src/prompts/snippets.ts Outdated
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations, e.g., "Can you tell me how to"). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, or whenever the user explicitly instructs you NOT to make changes just yet (e.g., "Don't make changes just yet", "Without changing anything"), your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a subsequent Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. ${options.interactive ? 'For Directives, only clarify if critically underspecified; otherwise, work autonomously.' : 'For Directives, you must work autonomously as no further user input is available.'} You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations, e.g., "Can you tell me how to"). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, or whenever the user explicitly instructs you NOT to make changes just yet (e.g., "Don't make changes just yet", "Without changing anything", "wait", "hold", "explain first", "but not now"), your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a subsequent Directive is issued. **IMPORTANT:** This constraint is programmatically enforced. If you attempt to call mutating tools (write_file, edit, shell) while a hold directive is active, they will be blocked at the system level and return a [HOLD DIRECTIVE ACTIVE] error. Only read-only tools (read_file, grep, glob, ls, web_search) remain available. Present your findings to the user and explicitly wait for authorization (e.g., "apply the fix", "go ahead", "proceed") before calling any mutating tools. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. ${options.interactive ? 'For Directives, only clarify if critically underspecified; otherwise, work autonomously.' : 'For Directives, you must work autonomously as no further user input is available.'} You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Avoid hardcoding tool names like write_file, edit, shell, read_file, grep, and glob in the prompt text. Since these tool name constants are already imported at the top of the file, using the imported constants (e.g., ${WRITE_FILE_TOOL_NAME}, ${EDIT_TOOL_NAME}, ${SHELL_TOOL_NAME}) ensures consistency and prevents potential mismatches if tool names are renamed in the future.

Suggested change
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations, e.g., "Can you tell me how to"). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, or whenever the user explicitly instructs you NOT to make changes just yet (e.g., "Don't make changes just yet", "Without changing anything", "wait", "hold", "explain first", "but not now"), your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a subsequent Directive is issued. **IMPORTANT:** This constraint is programmatically enforced. If you attempt to call mutating tools (write_file, edit, shell) while a hold directive is active, they will be blocked at the system level and return a [HOLD DIRECTIVE ACTIVE] error. Only read-only tools (read_file, grep, glob, ls, web_search) remain available. Present your findings to the user and explicitly wait for authorization (e.g., "apply the fix", "go ahead", "proceed") before calling any mutating tools. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. ${options.interactive ? 'For Directives, only clarify if critically underspecified; otherwise, work autonomously.' : 'For Directives, you must work autonomously as no further user input is available.'} You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations, e.g., "Can you tell me how to"). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, or whenever the user explicitly instructs you NOT to make changes just yet (e.g., "Don't make changes just yet", "Without changing anything", "wait", "hold", "explain first", "but not now"), your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a subsequent Directive is issued. **IMPORTANT:** This constraint is programmatically enforced. If you attempt to call mutating tools (${WRITE_FILE_TOOL_NAME}, ${EDIT_TOOL_NAME}, ${SHELL_TOOL_NAME}) while a hold directive is active, they will be blocked at the system level and return a [HOLD DIRECTIVE ACTIVE] error. Only read-only tools (${READ_FILE_TOOL_NAME}, ${GREP_TOOL_NAME}, ${GLOB_TOOL_NAME}, ls, web_search) remain available. Present your findings to the user and explicitly wait for authorization (e.g., "apply the fix", "go ahead", "proceed") before calling any mutating tools. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. ${options.interactive ? 'For Directives, only clarify if critically underspecified; otherwise, work autonomously.' : 'For Directives, you must work autonomously as no further user input is available.'} You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.

…ls at scheduler layer

Resolves google-gemini#26390

## Summary

The Gemini CLI agent exhibits an aggressive action-bias that overrides
explicit user hold directives. When users say 'wait', 'explain first',
or 'don't apply fixes yet', the agent still fires destructive tool calls
(replace, write_file, run_shell_command). Prompt-level instructions alone
are insufficient — the model's RLHF-driven completion bias overwhelms
the attention weights on negative constraints.

This fix adds programmatic enforcement at the scheduler layer:

## Details

1. **User Directive Detection Service** (userDirectiveService.ts):
   Detects hold/wait/explain directives from user messages using 14
   regex patterns across 4 directive types (EXPLICIT_WAIT, EXPLAIN_ONLY,
   NO_CHANGES_YET, REVIEW_FIRST). Includes negation guards to avoid
   false positives on 'don't wait' / 'go ahead' / 'proceed'.

2. **Hold Directive Guard** (holdDirectiveGuard.ts): Checks tool calls
   against the active hold directive at the scheduler layer, before they
   reach the policy/confirmation/execution pipeline. Only mutating tools
   (write_file, replace, run_shell_command, write_todos, invoke_agent)
   are blocked. Read-only tools (read_file, grep, glob, ls, web_search)
   remain available for continued investigation.

3. **Scheduler Integration** (scheduler.ts): The hold directive guard
   runs in _startBatch() before tool validation. Blocked calls receive
   an immediate error with a structured [HOLD DIRECTIVE ACTIVE] message
   that instructs the model to present findings and wait for explicit
   authorization.

4. **Strengthened System Prompt** (snippets.ts): The Expertise and
   Intent Alignment section now documents the programmatic enforcement,
   telling the model that mutating tools will be system-blocked during
   hold directives.

5. **New Error Type** (tool-error.ts): Added HOLD_DIRECTIVE_VIOLATION
   for precise error categorization in telemetry and logging.

6. **AgentLoopContext Extension** (agent-loop-context.ts): Added
   optional activeHoldDirective field for context-wide directive state.

## Test Coverage

- 48 unit tests for directive detection (including all 3 scenarios from
  issue google-gemini#26390, negation patterns, edge cases)
- 20 unit tests for the hold directive guard (mutating/read-only/batch)
- 9 end-to-end integration tests simulating the exact issue scenarios

## How to Validate

npm test -w @google/gemini-cli-core -- src/services/userDirectiveService.test.ts --run
npm test -w @google/gemini-cli-core -- src/scheduler/holdDirectiveGuard.test.ts --run
npm test -w @google/gemini-cli-core -- src/services/holdDirectiveIntegration.test.ts --run

## Related Issues

Resolves google-gemini#26390
@dylanyunlon
dylanyunlon force-pushed the fix/hold-directive-enforcement-26390 branch from fb4a3cb to c9b9bf5 Compare September 18, 2026 14:58
@dylanyunlon

Copy link
Copy Markdown
Author

Round 4 review findings addressed:

Reviews #14-17 (high) - [^.\n] -> [^.!?\n]: All 4 regex character classes now exclude ! and ? in addition to . and \n, preventing matching across sentences like "do not apply! Now, let's write the file."

Review #18 (high) - Global negation bypasses compound sentences: Rewrote detection to split input into clauses (by .!?\n and ", but" / "; ") and evaluate negation per-clause. "go ahead with research, but do not modify files yet" now correctly detects the hold in the second clause. Added 2 new tests for this.

Review #19 (high) - Hardcoded tool names in prompt: Replaced write_file, edit, shell, read_file, grep, glob with ${WRITE_FILE_TOOL_NAME}, ${EDIT_TOOL_NAME}, etc. using the constants already imported in snippets.ts.

All 86 tests pass. CLI run: [HOLD DIRECTIVE ACTIVE] blocks replace, source unchanged.

@dylanyunlon

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a programmatic 'Hold Directive Guard' mechanism to enforce user constraints (such as 'don't apply yet' or 'explain first') at the scheduler layer. It detects hold directives from user input using regex patterns, blocks mutating tool calls (like write_file, replace, and run_shell_command) while allowing read-only tools (like read_file and grep), and returns descriptive error messages to guide the model. It also updates prompt guidelines and adds comprehensive unit and integration tests. There are no review comments provided, so I have no feedback to offer on the review itself.

@dylanyunlon

dylanyunlon commented Sep 18, 2026 •

Copy link
Copy Markdown
Author

gemini-code-assist Review: PASSED

Round 5 review returned 0 inline comments, all previous findings resolved.

"There are no review comments provided, so I have no feedback to offer on the review itself."

Review History

Round Findings Status
1 1 critical + 5 high Fixed
2 5 high Fixed
3 2 high Fixed
4 6 high Fixed
5 0 Passed

Summary

  • 14 files changed, +1370 / -1
  • 86 tests, all passing
  • 5 review rounds, 19 findings resolved
  • CLI validated against mock API: hold directive blocks replace tool call, source file unchanged

@gemini-cli

gemini-cli Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

Hi there! Thank you for your interest in contributing to Gemini CLI.

To ensure we maintain high code quality and focus on our prioritized roadmap, we only guarantee review and consideration of pull requests for issues that are explicitly labeled as 'help wanted'.

This PR will be closed in 7 days if it remains without that designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

@gemini-cli

gemini-cli Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

This pull request is being closed as it has been open for 14 days without a 'help wanted' designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

This branch is waiting to be deployed

1 waiting deployment
eval-gate — c9b9bf54 Waiting Sep 18, 2026 by dylanyunlon via Evaluate Steering & Regressions #1990
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality priority/p1 Important and should be addressed in the near term. size/xl An extra large PR status/pr-nudge-sent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Severe Action-Bias Overriding Explicit User Hold Directives and Workflow Constraints, Disrespect for Gemini.md Constraints

1 participant