Skip to content

feat(pr-generator-orchestrator): implement iterative bug-fixing state machine and container worker entrypoint - #28433

Merged
joneba-google merged 6 commits into
google-gemini:mainfrom
JonE01:pr-generator-orchestrator
Aug 5, 2026
Merged

feat(pr-generator-orchestrator): implement iterative bug-fixing state machine and container worker entrypoint#28433
joneba-google merged 6 commits into
google-gemini:mainfrom
JonE01:pr-generator-orchestrator

Conversation

@joneba-google

@joneba-google joneba-google commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Overview

This PR implements the main application orchestration layer and asynchronous container entrypoint for the Gemini CLI SSR Pipeline. It coordinates Firestore concurrency locking, iterative AI agent coding and evaluation loops, ESLint static analysis, diff limit verification, and automated GitHub Pull Request submissions.
──────

Summary of Changes

1. Container Worker Entrypoint (worker.py)

• Asynchronous Main Runner (worker.py): Initializes the config.py object and executes the asynchronous orchestrator.py state machine.
• Centralized Logging: Establishes standardized stream logging with a custom log filter (worker.py) to eliminate raw WebSocket noise from container stdout.
• Structured Exit Codes: Maps orchestrator.py and unhandled exceptions to standardized exit codes for Cloud Run monitoring.

2. Orchestrator State Machine (orchestrator.py)

• Firestore Dual-Lock Validation: Invokes db_interface.py to claim issue leases (status: COMMIT_GENERATION), exiting cleanly if locked by another active workflow or escalating to NEEDS_HUMAN when exceeding
retry thresholds.
• Git Workspace Management: Clones/syncs target repositories, checks out feature branches (ssr-agent-<issue_num>), and configures git exclude rules to protect internal state files (firestore_doc.json,
changes.diff, verdict.json).
• Iterative Dual-Agent Loop:
• Coding Agent Phase: Prompts the Antigravity Coding Agent to implement code fixes and test assertions based on the Firestore issue specification or previous loop feedback (pr_feedback.md).
• Evaluation Agent Phase: Syncs workspace modifications into an isolated evaluation environment, runs ESLint checks, and assesses test verdicts.
• Evaluation Performance Optimization: Reuses node_modules from the PR workspace via filesystem symlinks (orchestrator.py), bypassing redundant 1–3+ minute npm ci installs on every loop iteration.
• Diff Limits & PR Publishing:
• Enforces a 500-line modified code limit; patches exceeding 500 lines are routed to NEEDS_HUMAN.
• Injects in-memory HTTP Basic Authorization headers (GIT_CONFIG_COUNT, GIT_CONFIG_KEY_0, GIT_CONFIG_VALUE_0) during git push to prevent plaintext token exposure in .git/config.
• Submits Pull Requests via github_client.py and marks Firestore documents as PR_EVALUATION_PENDING.

@joneba-google
joneba-google requested a review from a team as a code owner July 17, 2026 18:39
@github-actions github-actions Bot added the size/xl An extra large PR label Jul 17, 2026
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

📊 PR Size: size/XL

  • Lines changed: 1025
  • Additions: +1025
  • Deletions: -0
  • Files changed: 4

@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 the complete orchestration framework for an automated code generation pipeline, designed to transform issues into pull requests autonomously. It encompasses the entire workflow from initial issue intake and concurrency management to iterative AI-driven code development, rigorous evaluation, and final GitHub PR submission. The system is built to be deployed as a Cloud Run Job, leveraging Google Cloud services and AI agents to streamline the development process.

Highlights

  • Core Orchestration Layer: Implemented the main application orchestration layer and asynchronous container entrypoint for the Gemini CLI Issue-to-PR Code Generation Pipeline, coordinating various automated processes.
  • Iterative AI Agent Loop: Introduced an iterative dual-agent loop for AI coding and evaluation, which includes prompting coding agents, syncing workspace modifications, running ESLint static analysis, and assessing test verdicts.
  • Firestore Concurrency and State Management: Developed robust Firestore concurrency locking mechanisms to claim issue leases, handle retry thresholds, and manage lifecycle state transitions (e.g., COMMIT_GENERATION, PR_EVALUATION_PENDING, NEEDS_HUMAN).
  • Automated GitHub PR Submission: Integrated automated GitHub Pull Request submission, including enforcement of diff limits (500-line modified code limit), secure handling of Git authentication tokens, and marking Firestore documents as PR_EVALUATION_PENDING.
  • Cloud Run Deployment Configuration: Configured the necessary Dockerfile, Kubernetes Job definition, and Google Cloud Workflow to deploy and manage the PR generator pipeline as a Cloud Run Job, ensuring proper environment setup and error handling.
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 the caretaker-agent orchestrator, which automates bug-fixing and pull request generation using Google Antigravity AI agents, Firestore synchronization, and GitHub integration. The code review highlights critical security vulnerabilities, primarily centered around command injection risks in orchestrator.py and command_executor.py due to direct shell interpolation of untrusted inputs (such as issue numbers, repository URLs, and LLM-generated commit messages). To address these, the reviewer recommends refactoring CommandExecutor.run to support executing commands as argument lists with shell=False. Other feedback includes sanitizing file paths against path traversal, restricting agent policies to mitigate prompt injection, and centralizing environment variable normalization to avoid scattered logic.

Comment thread tools/caretaker-agent/cloudrun/pr-generator/workflow/orchestrator.py Outdated
Comment thread tools/caretaker-agent/cloudrun/pr-generator/workflow/orchestrator.py Outdated
Comment thread tools/caretaker-agent/cloudrun/pr-generator/workflow/orchestrator.py Outdated
Comment thread tools/caretaker-agent/cloudrun/pr-generator/workflow/agent_runner.py Outdated
Comment thread tools/caretaker-agent/cloudrun/pr-generator/workflow/command_executor.py Outdated
Comment thread tools/caretaker-agent/cloudrun/pr-generator/workflow/orchestrator.py Outdated
Comment thread tools/caretaker-agent/cloudrun/pr-generator/workflow/orchestrator.py Outdated
Comment thread tools/caretaker-agent/cloudrun/pr-generator/workflow/command_executor.py Outdated
Comment thread tools/caretaker-agent/cloudrun/pr-generator/workflow/orchestrator.py Outdated
Comment thread tools/caretaker-agent/cloudrun/pr-generator/workflow/db/db_interface.py Outdated
@joneba-google
joneba-google force-pushed the pr-generator-orchestrator branch from 75972b8 to 82a8800 Compare July 17, 2026 18:50
@github-actions github-actions Bot added the size/l A large sized PR label Jul 17, 2026
@joneba-google joneba-google changed the title Pr generator orchestrator feat(orchestrator): implement iterative bug-fixing state machine and container worker entrypoint Jul 17, 2026
@joneba-google joneba-google changed the title feat(orchestrator): implement iterative bug-fixing state machine and container worker entrypoint feat(pr-generator-orchestrator): implement iterative bug-fixing state machine and container worker entrypoint Jul 17, 2026
@gemini-cli gemini-cli Bot added the status/need-issue Pull requests that need to have an associated issue. label Jul 17, 2026
Comment on lines +33 to +38
from command_executor import (
CommandExecutor,
CommandExecutionError,
sanitize_identifier,
sanitize_relative_path,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Importing non-existent functions sanitize_identifier and sanitize_relative_path from command_executor will cause a fatal ImportError on startup.

Recommended fix:
Remove them from the import statement.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I forgot to push the change for that file! I recently updated pr-generator-core with the change.

Comment on lines +42 to +49
from db import (
acquire_lock,
release_lock,
mark_pr_created,
mark_needs_human,
ClaimAction,
IssueStatus,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Importing directly from db will fail with an ImportError because the db directory has no __init__.py exposing the symbols. Imports must be from db.db_interface.

Recommended fix:

from db.db_interface import (
    acquire_lock,
    release_lock,
    mark_pr_created,
    mark_needs_human,
    ClaimAction,
    IssueStatus,
)

Comment on lines +188 to +191
try:
CommandExecutor.run(["git", "checkout", "-B", branch_name, "origin/main"], self.config.pr_repo_path)
except CommandExecutionError as e:
raise OrchestrationError(f"Failed to checkout feature branch {branch_name}: {e}") from e

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: Add a TODO in the code for responding to user comments.

# TODO: Add logic to fetch and checkout the existing branch if responding to user feedback
CommandExecutor.run(["git", "checkout", "-B", branch_name, "origin/main"], self.config.pr_repo_path)

Comment on lines +178 to +182
if claim_action == ClaimAction.NEEDS_HUMAN:
logging.warning(
"Triage attempts exceeded maximum allowed limit. Issue moved to NEEDS_HUMAN. Exiting."
)
sys.exit(1)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Exiting with sys.exit(1) when the lock claim fails due to max attempts reached causes the Cloud Run job to report failure, triggering unnecessary workflow retries and overwriting the Firestore state.

Recommended fix:

        if claim_action == ClaimAction.NEEDS_HUMAN:
            logging.warning(
                "Triage attempts exceeded maximum allowed limit. Issue moved to NEEDS_HUMAN. Exiting."
            )
            return

Comment on lines +259 to +275
if commit_line_count > 500:
logging.error(
"Verdict: APPROVED but modified line size (%s) exceeds 500 limit. Moving to NEEDS_HUMAN.",
commit_line_count,
)
try:
mark_needs_human(
lock_holder=execution_id,
reason=f"Commit modifications ({commit_line_count} lines) exceed 500 lines limit.",
doc_id=doc_id,
owner=owner,
repo=repo,
issue_number=issue_num,
)
except Exception as e:
logging.error("Failed to update Firestore status to NEEDS_HUMAN: %s", e)
sys.exit(2)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Exiting with sys.exit(2) when the commit line count exceeds 500 causes the workflow to trigger failure handling, which overwrites the specific NEEDS_HUMAN status and reason in Firestore.

Recommended fix:

            if commit_line_count > 500:
                logging.error(
                    "Verdict: APPROVED but modified line size (%s) exceeds 500 limit. Moving to NEEDS_HUMAN.",
                    commit_line_count,
                )
                try:
                    mark_needs_human(
                        lock_holder=execution_id,
                        reason=f"Commit modifications ({commit_line_count} lines) exceed 500 lines limit.",
                        doc_id=doc_id,
                        owner=owner,
                        repo=repo,
                        issue_number=issue_num,
                    )
                except Exception as e:
                    logging.error("Failed to update Firestore status to NEEDS_HUMAN: %s", e)
                return

Comment on lines +472 to +477
eslint_cmd = ["npx", "eslint"] + changed_files + ["--max-warnings", "0"]
eslint_env = {**os.environ, "NODE_OPTIONS": "--max-old-space-size=4096"}
try:
lint_result = CommandExecutor.run(
eslint_cmd, self.config.eval_repo_path, env=eslint_env
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Calling CommandExecutor.run with a list eslint_cmd while it executes with shell=True will execute only the first item in the list, causing the command to fail or behave unexpectedly.

Recommended fix:

            eslint_files = " ".join(changed_files)
            eslint_cmd = f"npx eslint --max-warnings 0 --no-error-on-unmatched-pattern --no-warn-ignored {eslint_files}"
            eslint_env = {**os.environ, "NODE_OPTIONS": "--max-old-space-size=4096"}
            try:
                lint_result = CommandExecutor.run(
                    eslint_cmd, self.config.eval_repo_path, env=eslint_env
                )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for catching this. A recent change to CommandExecutor set use_shell to isinstance(cmd, str), so it should execute properly now. I implemented your recommended flags.

Comment on lines +626 to +628
except CommandExecutionError as e:
logging.error("Failed to push git branch: %s", e)
sys.exit(3)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Using sys.exit(3) inside business logic helper methods bypasses structured exception handling and leaves locks uncleaned. It should raise OrchestrationError.

Recommended fix:

        except CommandExecutionError as e:
            logging.error("Failed to push git branch: %s", e)
            raise OrchestrationError(f"Failed to push git branch: {e}") from e

Comment on lines +652 to +654
except GitHubClientError as e:
logging.error("Pull request submission failed: %s", e)
sys.exit(3)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Using sys.exit(3) on PR creation failure bypasses exception handling and leaves locks uncleaned. It should raise OrchestrationError.

Recommended fix:

            except GitHubClientError as e:
                logging.error("Pull request submission failed: %s", e)
                raise OrchestrationError(f"Pull request submission failed: {e}") from e

Comment on lines +20 to +27
def setup_logging() -> None:
"""Sets up the root logger with a standardized format."""
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
)
logging.getLogger().addFilter(IgnoreRawWsMsgFilter())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In Python logging, filters added to a logger (including the root logger) are bypassed for records that propagate from child loggers. Only filters added directly to the logging handlers will filter all propagated records. As a result, logs generated via child loggers (e.g. inside orchestrator.py or config.py) containing 'RAW WS MSG' will not be filtered.

Recommended fix:

def setup_logging() -> None:
    """Sets up the root logger with a standardized format."""
    handler = logging.StreamHandler(sys.stdout)
    handler.addFilter(IgnoreRawWsMsgFilter())
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
        handlers=[handler],
    )

Comment on lines +15 to +17
class IgnoreRawWsMsgFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
return "RAW WS MSG" not in record.getMessage()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class 'IgnoreRawWsMsgFilter' is missing a docstring.

Recommended fix:

class IgnoreRawWsMsgFilter(logging.Filter):
    """Filter to ignore raw websocket messages in the log output."""
    def filter(self, record: logging.LogRecord) -> bool:
        return "RAW WS MSG" not in record.getMessage()

diff_stat = CommandExecutor.run("git diff --stat origin/main", self.config.pr_repo_path)
logging.info("Diff Stat summary:\n%s", diff_stat)
lines = diff_stat.split("\n")
last_line = lines[-1] if lines else ""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Executing diff_stat.split("\n") on git diff --stat output with a trailing newline makes lines[-1] an empty string "". Regex matching for insertions/deletions on lines[-1] fails, resulting in commit_line_count = 0 and completely bypassing the 500-line safety cap (if commit_line_count > 500:).

Suggested Fix:

lines = diff_stat.strip().split("\n")
last_line = lines[-1] if lines else ""
insertions = re.search(r"(\d+)\s+insertion", last_line)
deletions = re.search(r"(\d+)\s+deletion", last_line)


# Parse recommended PR Description (case-insensitive)
desc_match = re.search(
r"##\s*PR\s*Description\r?\n\s*(.+)",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

desc_match uses (.+) with re.DOTALL without a lookahead delimiter. If ## PR Description appears before other headers in pr_details.md, desc_match captures the rest of the file.

Suggested Fix:

desc_match = re.search(
    r"##\s*PR\s*Description\r?\n\s*(.+?)(?=\r?\n##|$)",
    details_content,
    re.IGNORECASE | re.DOTALL,
)

@adamfweidman adamfweidman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall looks good but please add unit tests

@gemini-cli

gemini-cli Bot commented Jul 25, 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.

@adamfweidman adamfweidman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM!

logging.info("Cleaning up evaluation repository path: %s", self.config.eval_dir)
if os.path.exists(self.config.eval_dir):
try:
shutil.rmtree(self.config.eval_dir)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If shutil.rmtree failed with an OSError, would stale evaluation files leak across runs since os.makedirs with exist_ok=True keeps existing contents?

@joneba-google
joneba-google added this pull request to the merge queue Aug 5, 2026
Merged via the queue into google-gemini:main with commit b6b41f7 Aug 5, 2026
34 checks passed
@joneba-google
joneba-google deleted the pr-generator-orchestrator branch August 5, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

help wanted We will accept PRs from all issues marked as "help wanted". Thanks for your support! size/l A large sized PR size/xl An extra large PR status/need-issue Pull requests that need to have an associated issue. status/pr-nudge-sent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants