diff --git a/ci-operator/config/openshift/hypershift/openshift-hypershift-main.yaml b/ci-operator/config/openshift/hypershift/openshift-hypershift-main.yaml
index 9a34943d85e51..5d17803f9ca5e 100644
--- a/ci-operator/config/openshift/hypershift/openshift-hypershift-main.yaml
+++ b/ci-operator/config/openshift/hypershift/openshift-hypershift-main.yaml
@@ -1,4 +1,8 @@
base_images:
+ claude-ai-helpers:
+ name: claude-ai-helpers
+ namespace: ci
+ tag: latest
cli:
name: "4.22"
namespace: ocp
@@ -477,6 +481,12 @@ tests:
dependencies:
OPENSHIFT_INSTALL_RELEASE_IMAGE_OVERRIDE: release:latest-421
workflow: hypershift-aws-reqserving-e2e
+- as: periodic-jira-agent
+ cron: 30 8 * * 1
+ steps:
+ env:
+ JIRA_AGENT_MAX_ISSUES: "1"
+ workflow: hypershift-jira-agent
zz_generated_metadata:
branch: main
org: openshift
diff --git a/ci-operator/jobs/openshift/hypershift/openshift-hypershift-main-periodics.yaml b/ci-operator/jobs/openshift/hypershift/openshift-hypershift-main-periodics.yaml
index f57a81480dc21..265c46b4093ef 100644
--- a/ci-operator/jobs/openshift/hypershift/openshift-hypershift-main-periodics.yaml
+++ b/ci-operator/jobs/openshift/hypershift/openshift-hypershift-main-periodics.yaml
@@ -147,3 +147,61 @@ periodics:
- name: result-aggregator
secret:
secretName: result-aggregator
+- agent: kubernetes
+ cluster: build07
+ cron: 30 8 * * 1
+ decorate: true
+ extra_refs:
+ - base_ref: main
+ org: openshift
+ repo: hypershift
+ labels:
+ ci.openshift.io/generator: prowgen
+ pj-rehearse.openshift.io/can-be-rehearsed: "true"
+ name: periodic-ci-openshift-hypershift-main-periodic-jira-agent
+ spec:
+ containers:
+ - args:
+ - --gcs-upload-secret=/secrets/gcs/service-account.json
+ - --image-import-pull-secret=/etc/pull-secret/.dockerconfigjson
+ - --report-credentials-file=/etc/report/credentials
+ - --secret-dir=/secrets/ci-pull-credentials
+ - --target=periodic-jira-agent
+ command:
+ - ci-operator
+ image: quay-proxy.ci.openshift.org/openshift/ci:ci_ci-operator_latest
+ imagePullPolicy: Always
+ name: ""
+ resources:
+ requests:
+ cpu: 10m
+ volumeMounts:
+ - mountPath: /secrets/ci-pull-credentials
+ name: ci-pull-credentials
+ readOnly: true
+ - mountPath: /secrets/gcs
+ name: gcs-credentials
+ readOnly: true
+ - mountPath: /secrets/manifest-tool
+ name: manifest-tool-local-pusher
+ readOnly: true
+ - mountPath: /etc/pull-secret
+ name: pull-secret
+ readOnly: true
+ - mountPath: /etc/report
+ name: result-aggregator
+ readOnly: true
+ serviceAccountName: ci-operator
+ volumes:
+ - name: ci-pull-credentials
+ secret:
+ secretName: ci-pull-credentials
+ - name: manifest-tool-local-pusher
+ secret:
+ secretName: manifest-tool-local-pusher
+ - name: pull-secret
+ secret:
+ secretName: registry-pull-credentials
+ - name: result-aggregator
+ secret:
+ secretName: result-aggregator
diff --git a/ci-operator/step-registry/hypershift/jira-agent/OWNERS b/ci-operator/step-registry/hypershift/jira-agent/OWNERS
new file mode 100644
index 0000000000000..e39269bf55090
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/OWNERS
@@ -0,0 +1,12 @@
+approvers:
+- bryan-cox
+- csrwng
+- celebdor
+- enxebre
+- sjenning
+reviewers:
+- bryan-cox
+- csrwng
+- celebdor
+- enxebre
+- sjenning
diff --git a/ci-operator/step-registry/hypershift/jira-agent/README.md b/ci-operator/step-registry/hypershift/jira-agent/README.md
new file mode 100644
index 0000000000000..21f6eba19edd0
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/README.md
@@ -0,0 +1,285 @@
+# HyperShift Jira Agent Workflow
+
+Automated periodic job that processes Jira issues labeled with `issue-for-agent` and creates pull requests using Claude Code.
+
+## Overview
+
+This workflow implements a fully automated system for processing HyperShift Jira issues:
+
+1. **Query**: Searches Jira for unresolved issues in OCPBUGS and CNTRLPLANE projects with label `issue-for-agent` (excluding those with `agent-processed`)
+2. **Process**: For each issue, runs the `/jira-solve` command from the HyperShift repository non-interactively
+3. **Track**: Adds `agent-processed` label to successfully processed issues to prevent reprocessing
+
+## Data Flow Diagram
+
+```mermaid
+flowchart TD
+ %% Trigger
+ Start([Cron Trigger
Daily 9:00 AM UTC]):::trigger --> PrePhase
+
+ %% PRE-PHASE: Setup
+ subgraph PrePhase[PRE-PHASE: Setup]
+ direction TB
+ Verify[Verify Claude Code CLI
claude --version]:::setup
+ end
+
+ %% TEST-PHASE: Process
+ PrePhase --> TestPhase
+
+ subgraph TestPhase[TEST-PHASE: Process Issues]
+ direction TB
+
+ CloneRepos[Clone Repositories
ai-helpers + hypershift-community/hypershift]:::setup
+ CopyCommand[Copy jira-solve command
to .claude/commands/]:::setup
+ GitConfig[Configure Git
user: OpenShift CI Bot]:::setup
+ GenTokens[Generate GitHub App Tokens
JWT auth for fork + upstream]:::setup
+
+ QueryJira[Query Jira API
JQL: status in New, To Do
AND labels = issue-for-agent
AND labels != agent-processed]:::process
+
+ CheckIssues{Issues
Found?}:::decision
+ CheckMax{Processed <
MAX_ISSUES
Default: 1}:::decision
+ CheckSuccess{Processing
Successful?}:::decision
+
+ ProcessIssue[Run Claude Code CLI
--system-prompt jira-solve.md
--max-turns 100]:::ai
+
+ AddLabel[Add label
agent-processed
to Jira issue]:::success
+ LogFailure[Log failure
Will retry next run]:::failure
+ NoIssues[Exit: No issues to process]:::skip
+
+ RateLimit[Wait 60 seconds
Rate limiting]:::process
+ Summary[Print Summary
Processed/Failed counts]:::process
+
+ CloneRepos --> CopyCommand --> GitConfig --> GenTokens --> QueryJira
+ QueryJira --> CheckIssues
+ CheckIssues -->|No| NoIssues
+ CheckIssues -->|Yes| CheckMax
+ CheckMax -->|No| Summary
+ CheckMax -->|Yes| ProcessIssue
+ ProcessIssue --> CheckSuccess
+ CheckSuccess -->|Yes| AddLabel
+ CheckSuccess -->|No| LogFailure
+ AddLabel --> RateLimit
+ LogFailure --> RateLimit
+ RateLimit --> CheckMax
+ end
+
+ %% Secrets
+ Secret1[(Secret:
hypershift-team-claude-prow
app-id, private-key,
installation-ids)]:::secret -.->|GitHub App auth| GenTokens
+ Secret1 -.->|Vertex AI auth| ProcessIssue
+
+ %% External Systems
+ JiraAPI[(Jira API
issues.redhat.com)]:::external -.->|Return issues| QueryJira
+ JiraAPI -.->|Add label| AddLabel
+ ClaudeAPI[(Claude API
via Vertex AI)]:::external -.->|Generate solution| ProcessIssue
+ GitHubAPI[(GitHub API)]:::external -.->|Push to fork| ProcessIssue
+ GitHubAPI -.->|Create PR to upstream| ProcessIssue
+
+ TestPhase --> End([Workflow Complete]):::trigger
+ NoIssues --> End
+ Summary --> End
+
+ %% Style Definitions
+ classDef trigger fill:#e1f5ff,stroke:#01579b,stroke-width:3px,color:#000
+ classDef setup fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#000
+ classDef process fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px,color:#000
+ classDef decision fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000
+ classDef ai fill:#fce4ec,stroke:#880e4f,stroke-width:3px,color:#000
+ classDef success fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px,color:#000
+ classDef failure fill:#ffcdd2,stroke:#c62828,stroke-width:2px,color:#000
+ classDef skip fill:#f5f5f5,stroke:#757575,stroke-width:1px,color:#000
+ classDef external fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
+ classDef secret fill:#ffebee,stroke:#b71c1c,stroke-width:2px,color:#000
+```
+
+## Components
+
+### Workflow
+- **File**: `hypershift-jira-agent-workflow.yaml`
+- **Description**: Defines the two-phase workflow (pre/test)
+
+### Steps
+
+#### 1. Setup (`hypershift-jira-agent-setup`)
+- Verifies Claude Code CLI is available
+
+#### 2. Process (`hypershift-jira-agent-process`)
+- Clones ai-helpers and hypershift-community/hypershift repositories
+- Copies jira-solve command to `.claude/commands/`
+- Configures git and generates GitHub App tokens (JWT auth)
+- Queries Jira API for labeled issues (excluding those with `agent-processed`)
+- Runs jira-solve for each issue using Claude Code CLI with `--system-prompt`
+- Pushes branches to fork, creates PRs to upstream openshift/hypershift
+- Implements rate limiting (60s between issues)
+- Adds `agent-processed` label to successfully processed issues
+
+## Configuration
+
+### Secrets Required
+
+The workflow requires a single secret in the `test-credentials` namespace:
+
+**`hypershift-team-claude-prow`**
+- Mount path: `/var/run/claude-code-service-account`
+- Required keys:
+ - `claude-prow`: GCP service account JSON key for Vertex AI authentication
+ - `app-id`: GitHub App ID
+ - `private-key`: GitHub App private key for JWT signing
+ - `installation-id`: GitHub App installation ID for hypershift-community fork
+ - `o-h-installation-id`: GitHub App installation ID for openshift/hypershift upstream
+
+The workflow uses GitHub App authentication (JWT-based) rather than personal access tokens. This provides better security and allows fine-grained permissions.
+
+**Optional** (currently disabled for testing):
+- `hypershift-jira-token`: Jira API token for adding `agent-processed` labels
+
+These should be configured in Vault with secretsync metadata and synced automatically.
+
+### Periodic Job
+
+Configured in `ci-operator/config/openshift/hypershift/openshift-hypershift-main.yaml`:
+
+```yaml
+- as: periodic-jira-agent
+ cron: 0 9 * * * # Daily at 9:00 AM UTC
+ steps:
+ env:
+ JIRA_AGENT_MAX_ISSUES: "1" # Start with 1 for testing, increase later
+ workflow: hypershift-jira-agent
+```
+
+### Environment Variables
+
+- **`JIRA_AGENT_MAX_ISSUES`** (default: `1`)
+ - Maximum number of issues to process per run
+ - Set to `1` initially for safe testing
+ - Can be increased to `5`, `10`, or higher once validated
+ - Counts both successful and failed processing attempts
+
+### State Management
+
+State is tracked using Jira labels:
+- **Label**: `agent-processed`
+- When an issue is successfully processed, the `agent-processed` label is added
+- The JQL query excludes issues with this label, preventing reprocessing
+- Failed issues are NOT labeled, allowing automatic retry on subsequent runs
+
+To reprocess an issue:
+1. Remove the `agent-processed` label from the Jira issue
+2. The issue will be picked up on the next run
+
+## How It Works
+
+### Non-Interactive Execution
+
+The workflow uses Claude Code CLI's non-interactive mode with a system prompt:
+
+```bash
+claude -p "$ISSUE_KEY origin --ci" \
+ --system-prompt "$SKILL_CONTENT" \
+ --allowedTools "Bash Read Write Edit Grep Glob WebFetch" \
+ --max-turns 100 \
+ --verbose \
+ --output-format stream-json
+```
+
+The jira-solve command is loaded from `ai-helpers/plugins/jira/commands/solve.md` and passed as a system prompt. This allows Claude to analyze the Jira issue and create a PR automatically.
+
+### Jira Query
+
+Issues are queried using JQL:
+```
+project in (OCPBUGS, CNTRLPLANE) AND resolution = Unresolved AND status in (New, "To Do") AND labels = issue-for-agent AND labels != agent-processed
+```
+
+Maximum issues queried and processed is controlled by `JIRA_AGENT_MAX_ISSUES` (default: 1).
+
+### Rate Limiting
+
+- 60 seconds between processing each issue
+- Maximum 100 agentic turns per issue
+- Maximum issues per run: configurable via `JIRA_AGENT_MAX_ISSUES`
+- Runs once daily at 9:00 AM UTC
+
+## Container Image
+
+Uses the `claude-ai-helpers` image from OpenShift CI containing:
+- Claude Code CLI
+- GitHub CLI (gh)
+- jq, git, curl
+- Required dependencies
+
+## Local Testing
+
+Use the test script:
+
+```bash
+export ANTHROPIC_API_KEY=your-key
+export GITHUB_TOKEN=your-token
+./tools/hypershift-jira-agent/test-locally.sh
+```
+
+## Monitoring
+
+### Success Indicators
+- Issues processed successfully with PRs created
+- `agent-processed` label added to processed issues
+- No authentication errors
+
+### Failure Indicators
+- Failed to authenticate with Claude API
+- Failed to create PRs (GitHub auth issues)
+- Individual issue processing failures
+
+### Logs
+Check Prow job logs for:
+- Jira query results
+- Processing output for each issue
+- PR URLs created
+- Error messages
+
+## Maintenance
+
+### Adding/Removing Issues
+Add or remove the `issue-for-agent` label in Jira to control which issues are processed.
+
+### Reprocessing an Issue
+To reprocess an issue, remove the `agent-processed` label from the Jira issue:
+1. Open the issue in Jira
+2. Remove the `agent-processed` label
+3. The issue will be picked up on the next scheduled run
+
+### Adjusting Frequency
+Modify the `cron` schedule in the CI config file. Currently runs daily at 9:00 AM UTC.
+
+### Adjusting Issue Limit
+Modify the `JIRA_AGENT_MAX_ISSUES` environment variable in the CI config file:
+```yaml
+env:
+ JIRA_AGENT_MAX_ISSUES: "5" # Increase from 1 to 5
+```
+Then run `make update` to regenerate job configs.
+
+## Troubleshooting
+
+### Issue: No issues being processed
+- Check Jira query returns results
+- Verify `issue-for-agent` label exists on issues
+- Verify `agent-processed` label is NOT on issues (or remove it to reprocess)
+
+### Issue: Authentication failures
+- Verify secrets are mounted correctly
+- Check API keys are valid and not expired
+- Ensure GitHub token has required permissions
+
+### Issue: PR creation fails
+- Check GitHub token permissions
+- Verify HyperShift repository access
+- Review `/jira-solve` command output in logs
+
+## Future Enhancements
+
+- Slack notifications for processed issues
+- Metrics push to Prometheus
+- Automatic retries for transient failures
+- Priority-based processing
+- Issue assignment tracking
diff --git a/ci-operator/step-registry/hypershift/jira-agent/hypershift-jira-agent-workflow.metadata.json b/ci-operator/step-registry/hypershift/jira-agent/hypershift-jira-agent-workflow.metadata.json
new file mode 100644
index 0000000000000..21e88ab164d34
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/hypershift-jira-agent-workflow.metadata.json
@@ -0,0 +1,19 @@
+{
+ "path": "hypershift/jira-agent/hypershift-jira-agent-workflow.yaml",
+ "owners": {
+ "approvers": [
+ "bryan-cox",
+ "csrwng",
+ "celebdor",
+ "enxebre",
+ "sjenning"
+ ],
+ "reviewers": [
+ "bryan-cox",
+ "csrwng",
+ "celebdor",
+ "enxebre",
+ "sjenning"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/ci-operator/step-registry/hypershift/jira-agent/hypershift-jira-agent-workflow.yaml b/ci-operator/step-registry/hypershift/jira-agent/hypershift-jira-agent-workflow.yaml
new file mode 100644
index 0000000000000..5c8ede6c50d35
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/hypershift-jira-agent-workflow.yaml
@@ -0,0 +1,16 @@
+workflow:
+ as: hypershift-jira-agent
+ steps:
+ pre:
+ - ref: hypershift-jira-agent-setup
+ test:
+ - ref: hypershift-jira-agent-process
+ documentation: |-
+ HyperShift Jira Agent workflow for automated issue processing.
+
+ This workflow:
+ 1. Setup: Verifies Claude Code CLI is available
+ 2. Process: Queries Jira for labeled issues, runs /jira-solve for each, creates PRs
+
+ The workflow uses the /jira-solve command in non-interactive mode.
+ Issues are queried from Jira with: project in (OCPBUGS, CNTRLPLANE) AND status in (New, "To Do") AND labels = issue-for-agent
diff --git a/ci-operator/step-registry/hypershift/jira-agent/process/OWNERS b/ci-operator/step-registry/hypershift/jira-agent/process/OWNERS
new file mode 100644
index 0000000000000..e39269bf55090
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/process/OWNERS
@@ -0,0 +1,12 @@
+approvers:
+- bryan-cox
+- csrwng
+- celebdor
+- enxebre
+- sjenning
+reviewers:
+- bryan-cox
+- csrwng
+- celebdor
+- enxebre
+- sjenning
diff --git a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-commands.sh b/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-commands.sh
new file mode 100755
index 0000000000000..e1659ce954836
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-commands.sh
@@ -0,0 +1,318 @@
+#!/bin/bash
+set -euo pipefail
+
+echo "=== HyperShift Jira Agent Process ==="
+
+# State file for sharing results with report step
+STATE_FILE="${SHARED_DIR}/processed-issues.txt"
+
+# Clone ai-helpers repository (contains /jira-solve command)
+echo "Cloning ai-helpers repository..."
+git clone https://github.com/openshift-eng/ai-helpers /tmp/ai-helpers
+
+# Clone HyperShift fork (we push here and create PRs to upstream)
+echo "Cloning HyperShift repository..."
+git clone https://github.com/hypershift-community/hypershift /tmp/hypershift
+
+# Copy jira-solve command from ai-helpers to hypershift
+echo "Setting up Claude commands..."
+mkdir -p /tmp/hypershift/.claude/commands
+cp /tmp/ai-helpers/plugins/jira/commands/solve.md /tmp/hypershift/.claude/commands/jira-solve.md
+
+cd /tmp/hypershift
+
+# Configure git
+git config user.name "OpenShift CI Bot"
+git config user.email "ci-bot@redhat.com"
+
+# Generate GitHub App installation token
+echo "Generating GitHub App token..."
+
+GITHUB_APP_CREDS_DIR="/var/run/claude-code-service-account"
+APP_ID_FILE="${GITHUB_APP_CREDS_DIR}/app-id"
+INSTALLATION_ID_FILE="${GITHUB_APP_CREDS_DIR}/installation-id"
+PRIVATE_KEY_FILE="${GITHUB_APP_CREDS_DIR}/private-key"
+
+# Check if all required credentials exist
+INSTALLATION_ID_UPSTREAM_FILE="${GITHUB_APP_CREDS_DIR}/o-h-installation-id"
+
+if [ ! -f "$APP_ID_FILE" ] || [ ! -f "$INSTALLATION_ID_FILE" ] || [ ! -f "$PRIVATE_KEY_FILE" ] || [ ! -f "$INSTALLATION_ID_UPSTREAM_FILE" ]; then
+ echo "GitHub App credentials not yet available in ${GITHUB_APP_CREDS_DIR}"
+ echo "Available files:"
+ ls -la "${GITHUB_APP_CREDS_DIR}/" || echo "Directory does not exist"
+ echo ""
+ echo "Waiting for Vault secretsync to complete. The following keys are required:"
+ echo " - app-id"
+ echo " - installation-id (for hypershift-community fork)"
+ echo " - o-h-installation-id (for openshift/hypershift upstream)"
+ echo " - private-key"
+ echo ""
+ echo "Exiting gracefully. Re-run once secrets are synced."
+ exit 0
+fi
+
+APP_ID=$(cat "$APP_ID_FILE")
+INSTALLATION_ID_FORK=$(cat "$INSTALLATION_ID_FILE")
+INSTALLATION_ID_UPSTREAM=$(cat "$INSTALLATION_ID_UPSTREAM_FILE")
+
+# Function to generate GitHub App token for a given installation ID
+generate_github_token() {
+ local INSTALL_ID=$1
+ local NOW
+ NOW=$(date +%s)
+ local IAT=$((NOW - 60))
+ local EXP=$((NOW + 600))
+
+ local HEADER
+ HEADER=$(echo -n '{"alg":"RS256","typ":"JWT"}' | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
+ local PAYLOAD
+ PAYLOAD=$(echo -n "{\"iat\":${IAT},\"exp\":${EXP},\"iss\":\"${APP_ID}\"}" | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
+ local SIGNATURE
+ SIGNATURE=$(echo -n "${HEADER}.${PAYLOAD}" | openssl dgst -sha256 -sign "$PRIVATE_KEY_FILE" | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
+ local JWT="${HEADER}.${PAYLOAD}.${SIGNATURE}"
+
+ curl -s -X POST \
+ -H "Authorization: Bearer ${JWT}" \
+ -H "Accept: application/vnd.github+json" \
+ "https://api.github.com/app/installations/${INSTALL_ID}/access_tokens" \
+ | jq -r '.token'
+}
+
+# Generate token for fork (hypershift-community/hypershift) - for pushing branches
+echo "Generating GitHub App token for fork..."
+GITHUB_TOKEN_FORK=$(generate_github_token "$INSTALLATION_ID_FORK")
+if [ -z "$GITHUB_TOKEN_FORK" ] || [ "$GITHUB_TOKEN_FORK" = "null" ]; then
+ echo "ERROR: Failed to generate GitHub App token for fork"
+ exit 1
+fi
+echo "Fork token generated successfully"
+
+# Generate token for upstream (openshift/hypershift) - for creating PRs
+echo "Generating GitHub App token for upstream..."
+GITHUB_TOKEN_UPSTREAM=$(generate_github_token "$INSTALLATION_ID_UPSTREAM")
+if [ -z "$GITHUB_TOKEN_UPSTREAM" ] || [ "$GITHUB_TOKEN_UPSTREAM" = "null" ]; then
+ echo "ERROR: Failed to generate GitHub App token for upstream"
+ exit 1
+fi
+echo "Upstream token generated successfully"
+
+# Configure git to use the fork token for push operations via credential helper
+# Using credential helper instead of URL rewriting prevents token leaking in git remote output
+git config --global credential.helper "!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN_FORK}; }; f"
+
+# Export upstream token as GITHUB_TOKEN for gh CLI (used for PR creation)
+export GITHUB_TOKEN="$GITHUB_TOKEN_UPSTREAM"
+echo "GitHub App tokens configured successfully"
+
+# Configuration: maximum issues to process per run (default: 1)
+MAX_ISSUES=${JIRA_AGENT_MAX_ISSUES:-1}
+echo "Configuration: MAX_ISSUES=$MAX_ISSUES"
+
+# Load Jira API token for adding labels after processing
+JIRA_TOKEN_FILE="/var/run/claude-code-service-account/jira-pat"
+if [ -f "$JIRA_TOKEN_FILE" ]; then
+ JIRA_TOKEN=$(cat "$JIRA_TOKEN_FILE")
+ echo "Jira API token loaded from jira-pat"
+else
+ echo "Warning: Jira API token not found at $JIRA_TOKEN_FILE"
+ echo "Labels will not be added to processed issues"
+ JIRA_TOKEN=""
+fi
+
+# Function to transition a Jira issue to a target status
+transition_issue() {
+ local ISSUE_KEY=$1
+ local TARGET_STATUS=$2
+
+ # Get available transitions
+ TRANSITIONS=$(curl -s \
+ "https://issues.redhat.com/rest/api/2/issue/$ISSUE_KEY/transitions" \
+ -H "Authorization: Bearer $JIRA_TOKEN" \
+ -H "Content-Type: application/json")
+
+ # Find transition ID for target status (match by name)
+ TRANSITION_ID=$(echo "$TRANSITIONS" | jq -r --arg status "$TARGET_STATUS" \
+ '.transitions[] | select(.name == $status) | .id' | head -1)
+
+ if [ -n "$TRANSITION_ID" ] && [ "$TRANSITION_ID" != "null" ]; then
+ curl -s -X POST \
+ "https://issues.redhat.com/rest/api/2/issue/$ISSUE_KEY/transitions" \
+ -H "Authorization: Bearer $JIRA_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "{\"transition\":{\"id\":\"$TRANSITION_ID\"}}"
+ return 0
+ else
+ echo " Warning: Transition to '$TARGET_STATUS' not available"
+ return 1
+ fi
+}
+
+# Function to set assignee on a Jira issue
+set_assignee() {
+ local ISSUE_KEY=$1
+ local ASSIGNEE_NAME=$2
+
+ curl -s -w "\n%{http_code}" -X PUT \
+ "https://issues.redhat.com/rest/api/2/issue/$ISSUE_KEY/assignee" \
+ -H "Authorization: Bearer $JIRA_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "{\"name\":\"$ASSIGNEE_NAME\"}"
+}
+
+# Query Jira for issues (excluding already processed ones via label)
+echo "Querying Jira for issues..."
+ISSUES=$(curl -s "https://issues.redhat.com/rest/api/2/search" \
+ -G \
+ --data-urlencode 'jql=project in (OCPBUGS, CNTRLPLANE) AND resolution = Unresolved AND status in (New, "To Do") AND labels = issue-for-agent AND labels != agent-processed' \
+ --data-urlencode 'fields=key,summary' \
+ --data-urlencode "maxResults=$MAX_ISSUES" \
+ | jq -r '.issues[]? | "\(.key) \(.fields.summary)"')
+
+if [ -z "$ISSUES" ]; then
+ echo "No issues found matching criteria"
+ exit 0
+fi
+
+echo "Found issues:"
+echo "$ISSUES" | awk '{print " - " $1}'
+
+# Counters for summary
+PROCESSED_COUNT=0
+FAILED_COUNT=0
+TOTAL_PROCESSED_OR_FAILED=0
+
+# Process each issue
+while IFS= read -r line; do
+ # Stop if we've reached the max issues limit (counting both successful and failed)
+ if [ $TOTAL_PROCESSED_OR_FAILED -ge "$MAX_ISSUES" ]; then
+ echo "Reached maximum issues limit ($MAX_ISSUES). Stopping."
+ break
+ fi
+ ISSUE_KEY=$(echo "$line" | awk '{print $1}')
+ ISSUE_SUMMARY=$(echo "$line" | cut -d' ' -f2-)
+
+ echo ""
+ echo "=========================================="
+ echo "Processing: $ISSUE_KEY"
+ echo "Summary: $ISSUE_SUMMARY"
+ echo "=========================================="
+
+ # Run jira-solve command non-interactively using --system-prompt
+ # (Claude's -p mode doesn't support slash commands directly)
+ TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
+
+ echo "Running: jira-solve $ISSUE_KEY origin --ci"
+
+ # Load the skill content as system prompt
+ SKILL_CONTENT=$(cat /tmp/hypershift/.claude/commands/jira-solve.md)
+
+ # Additional context for fork-based workflow
+ # Git push uses fork token (configured via credential helper), gh CLI uses upstream token (GITHUB_TOKEN env var)
+ FORK_CONTEXT="IMPORTANT: You are working in a fork (hypershift-community/hypershift). Git push is pre-configured to work with the fork. After pushing the branch, you MUST create the PR by running: gh pr create --repo openshift/hypershift --head hypershift-community: --no-maintainer-edit --draft --title '' --body ''. The PR body MUST end with the following disclaimer on its own line: 'Always review AI generated responses prior to use.' The gh CLI is authenticated to openshift/hypershift. Do NOT skip PR creation - this is a required step. SECURITY: Do NOT run commands that reveal git credentials like 'git remote -v' or 'git remote get-url origin'."
+
+ set +e # Don't exit on error for individual issues
+ echo "Starting Claude processing with streaming output..."
+ RESULT=$(claude -p "$ISSUE_KEY origin --ci. $FORK_CONTEXT" \
+ --system-prompt "$SKILL_CONTENT" \
+ --allowedTools "Bash Read Write Edit Grep Glob WebFetch" \
+ --max-turns 100 \
+ --model "$CLAUDE_MODEL" \
+ --verbose \
+ --output-format stream-json \
+ 2>&1 | tee "/tmp/claude-${ISSUE_KEY}-output.json")
+ EXIT_CODE=$?
+ set -e
+ echo "Claude processing complete. Full output saved to /tmp/claude-${ISSUE_KEY}-output.json"
+
+ if [ $EXIT_CODE -eq 0 ]; then
+ # Parse PR URL from result if available
+ PR_URL=$(echo "$RESULT" | grep -oP 'https://github.com/openshift/hypershift/pull/[0-9]+' | head -1 || echo "")
+
+ echo "✅ Successfully processed $ISSUE_KEY"
+ if [ -n "$PR_URL" ]; then
+ echo " PR: $PR_URL"
+ # Add /auto-cc comment to assign reviewers
+ echo " Adding /auto-cc comment to assign reviewers..."
+ if gh pr comment "$PR_URL" --body "/auto-cc"; then
+ echo " /auto-cc comment added successfully"
+ else
+ echo " Warning: Failed to add /auto-cc comment"
+ fi
+ else
+ echo " Note: No PR URL found in output. Claude may have encountered an issue creating the PR."
+ fi
+
+ echo ""
+ echo "--- Claude output for $ISSUE_KEY ---"
+ echo "$RESULT"
+ echo "--- End Claude output ---"
+ echo ""
+
+ # Add 'agent-processed' label to mark issue as handled
+ if [ -n "$JIRA_TOKEN" ]; then
+ echo "Adding 'agent-processed' label to $ISSUE_KEY..."
+ LABEL_RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \
+ "https://issues.redhat.com/rest/api/2/issue/$ISSUE_KEY" \
+ -H "Authorization: Bearer $JIRA_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"update":{"labels":[{"add":"agent-processed"}]}}')
+ HTTP_CODE=$(echo "$LABEL_RESPONSE" | tail -1)
+ if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "200" ]; then
+ echo " Label added successfully"
+ else
+ echo " Warning: Failed to add label (HTTP $HTTP_CODE)"
+ fi
+
+ # Transition issue to appropriate status based on project
+ if [[ "$ISSUE_KEY" == OCPBUGS-* ]]; then
+ TARGET_STATUS="ASSIGNED"
+ else
+ TARGET_STATUS="Code Review"
+ fi
+
+ echo "Transitioning $ISSUE_KEY to '$TARGET_STATUS'..."
+ if transition_issue "$ISSUE_KEY" "$TARGET_STATUS"; then
+ echo " Transition successful"
+ else
+ echo " Transition failed or not available"
+ fi
+
+ # Set assignee to hypershift-automation
+ echo "Setting assignee to 'hypershift-automation'..."
+ ASSIGNEE_RESPONSE=$(set_assignee "$ISSUE_KEY" "hypershift-automation")
+ HTTP_CODE=$(echo "$ASSIGNEE_RESPONSE" | tail -1)
+ if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "200" ]; then
+ echo " Assignee set successfully"
+ else
+ echo " Warning: Failed to set assignee (HTTP $HTTP_CODE)"
+ fi
+ fi
+
+ PROCESSED_COUNT=$((PROCESSED_COUNT + 1))
+ echo "$ISSUE_KEY $TIMESTAMP $PR_URL SUCCESS" >> "$STATE_FILE"
+ else
+ # Log failure but don't mark as processed (will be retried next run)
+ echo "❌ Failed to process $ISSUE_KEY"
+ echo "Error output (last 20 lines):"
+ echo "$RESULT" | tail -20
+ FAILED_COUNT=$((FAILED_COUNT + 1))
+ echo "$ISSUE_KEY $TIMESTAMP - FAILED" >> "$STATE_FILE"
+ fi
+
+ # Increment total counter
+ TOTAL_PROCESSED_OR_FAILED=$((TOTAL_PROCESSED_OR_FAILED + 1))
+
+ # Rate limiting between issues (60 seconds)
+ # Skip sleep if we've reached the limit
+ if [ $TOTAL_PROCESSED_OR_FAILED -lt "$MAX_ISSUES" ]; then
+ echo "Waiting 60 seconds before next issue..."
+ sleep 60
+ fi
+
+done <<< "$ISSUES"
+
+echo ""
+echo "=== Processing Summary ==="
+echo "Processed: $PROCESSED_COUNT"
+echo "Failed: $FAILED_COUNT"
+echo "=========================="
diff --git a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.metadata.json b/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.metadata.json
new file mode 100644
index 0000000000000..22d12984f05fb
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.metadata.json
@@ -0,0 +1,19 @@
+{
+ "path": "hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml",
+ "owners": {
+ "approvers": [
+ "bryan-cox",
+ "csrwng",
+ "celebdor",
+ "enxebre",
+ "sjenning"
+ ],
+ "reviewers": [
+ "bryan-cox",
+ "csrwng",
+ "celebdor",
+ "enxebre",
+ "sjenning"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml b/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml
new file mode 100644
index 0000000000000..58e7f74c0e268
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml
@@ -0,0 +1,46 @@
+
+ref:
+ as: hypershift-jira-agent-process
+ from: claude-ai-helpers
+ commands: hypershift-jira-agent-process-commands.sh
+ env:
+ - name: CLAUDE_CODE_USE_VERTEX
+ default: "1"
+ documentation: |-
+ Enable Vertex AI for Claude Code.
+ - name: CLOUD_ML_REGION
+ default: "us-east5"
+ documentation: |-
+ Google Cloud region for Vertex AI.
+ - name: ANTHROPIC_VERTEX_PROJECT_ID
+ default: "itpc-gcp-hybrid-pe-eng-claude"
+ documentation: |-
+ Google Cloud project ID for Vertex AI authentication.
+ - name: GOOGLE_APPLICATION_CREDENTIALS
+ default: "/var/run/claude-code-service-account/claude-prow"
+ documentation: |-
+ Path to the Google Cloud service account JSON key file for Vertex AI authentication.
+ - name: JIRA_AGENT_MAX_ISSUES
+ default: "1"
+ documentation: |-
+ Maximum number of Jira issues to process per run. Defaults to 1 for conservative processing.
+ - name: CLAUDE_MODEL
+ default: "claude-opus-4-5"
+ documentation: |-
+ Claude model to use for processing Jira issues.
+ resources:
+ requests:
+ cpu: 500m
+ memory: 1Gi
+ credentials:
+ - namespace: test-credentials
+ name: hypershift-team-claude-prow
+ mount_path: /var/run/claude-code-service-account
+ documentation: |-
+ Process step for the HyperShift Jira agent periodic job.
+ This step:
+ - Queries Jira for issues with label 'issue-for-agent' (excluding 'agent-processed')
+ - For each issue, runs the /jira-solve command non-interactively
+ - On success, adds 'agent-processed' label to prevent reprocessing
+ - Failed issues are retried on subsequent runs
+ - Uses Vertex AI for Claude authentication via GCP service account
diff --git a/ci-operator/step-registry/hypershift/jira-agent/setup/OWNERS b/ci-operator/step-registry/hypershift/jira-agent/setup/OWNERS
new file mode 100644
index 0000000000000..e39269bf55090
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/setup/OWNERS
@@ -0,0 +1,12 @@
+approvers:
+- bryan-cox
+- csrwng
+- celebdor
+- enxebre
+- sjenning
+reviewers:
+- bryan-cox
+- csrwng
+- celebdor
+- enxebre
+- sjenning
diff --git a/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-commands.sh b/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-commands.sh
new file mode 100755
index 0000000000000..94cc7c5d99139
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-commands.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -euo pipefail
+
+echo "=== HyperShift Jira Agent Setup ==="
+
+# Verify Claude Code is available (Vertex AI authentication is handled via GOOGLE_APPLICATION_CREDENTIALS env var)
+echo "Verifying Claude Code CLI..."
+claude --version || { echo "ERROR: Claude Code CLI not found"; exit 1; }
+
+echo "Setup complete"
diff --git a/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.metadata.json b/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.metadata.json
new file mode 100644
index 0000000000000..59e74a6fdf8fb
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.metadata.json
@@ -0,0 +1,19 @@
+{
+ "path": "hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.yaml",
+ "owners": {
+ "approvers": [
+ "bryan-cox",
+ "csrwng",
+ "celebdor",
+ "enxebre",
+ "sjenning"
+ ],
+ "reviewers": [
+ "bryan-cox",
+ "csrwng",
+ "celebdor",
+ "enxebre",
+ "sjenning"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.yaml b/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.yaml
new file mode 100644
index 0000000000000..39aa028366a97
--- /dev/null
+++ b/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.yaml
@@ -0,0 +1,37 @@
+ref:
+ as: hypershift-jira-agent-setup
+ from: claude-ai-helpers
+ commands: hypershift-jira-agent-setup-commands.sh
+ env:
+ - name: CLAUDE_CODE_USE_VERTEX
+ default: "1"
+ documentation: |-
+ Enable Vertex AI for Claude Code.
+ - name: CLOUD_ML_REGION
+ default: "us-east5"
+ documentation: |-
+ Google Cloud region for Vertex AI.
+ - name: ANTHROPIC_VERTEX_PROJECT_ID
+ default: "itpc-gcp-hybrid-pe-eng-claude"
+ documentation: |-
+ Google Cloud project ID for Vertex AI authentication.
+ - name: GOOGLE_APPLICATION_CREDENTIALS
+ default: "/var/run/claude-code-service-account/claude-prow"
+ documentation: |-
+ Path to the Google Cloud service account JSON key file for Vertex AI authentication.
+ resources:
+ requests:
+ cpu: 100m
+ memory: 200Mi
+ credentials:
+ - namespace: test-credentials
+ name: hypershift-team-claude-prow
+ mount_path: /var/run/claude-code-service-account
+ documentation: |-
+ Setup step for the HyperShift Jira agent periodic job.
+ This step:
+ - Clones the HyperShift repository
+ - Configures git credentials for creating commits
+ - Sets up GitHub CLI authentication
+ - Verifies Claude Code CLI is available
+ - Uses Vertex AI for Claude authentication via GCP service account
diff --git a/docs/plans/2026-01-28-jira-agent-schedule-update.md b/docs/plans/2026-01-28-jira-agent-schedule-update.md
new file mode 100644
index 0000000000000..8b0c9fc55cd0c
--- /dev/null
+++ b/docs/plans/2026-01-28-jira-agent-schedule-update.md
@@ -0,0 +1,131 @@
+# Jira Agent Schedule Update Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Update the HyperShift Jira Agent periodic job to run weekly on Mondays at 8:30 AM UTC instead of daily at 9:00 AM UTC.
+
+**Architecture:** Single-file configuration change in the ci-operator config, followed by regeneration of Prow job files via `make update`.
+
+**Tech Stack:** YAML configuration, Prow CI, cron syntax
+
+---
+
+## Task 1: Update the cron schedule in CI config
+
+**Files:**
+- Modify: `ci-operator/config/openshift/hypershift/openshift-hypershift-main.yaml:485`
+
+**Step 1: Review current configuration**
+
+Current cron configuration at line 485:
+```yaml
+- as: periodic-jira-agent
+ cron: 0 9 * * *
+ steps:
+ env:
+ JIRA_AGENT_MAX_ISSUES: "1"
+ workflow: hypershift-jira-agent
+```
+
+The current `0 9 * * *` means "at 9:00 AM UTC every day".
+
+**Step 2: Update cron to weekly Monday at 8:30 AM UTC**
+
+Change the cron expression from `0 9 * * *` to `30 8 * * 1`:
+- `30` = minute 30
+- `8` = hour 8 (8:00 AM UTC)
+- `*` = any day of month
+- `*` = any month
+- `1` = Monday (day of week, 0=Sunday, 1=Monday)
+
+```yaml
+- as: periodic-jira-agent
+ cron: 30 8 * * 1
+ steps:
+ env:
+ JIRA_AGENT_MAX_ISSUES: "1"
+ workflow: hypershift-jira-agent
+```
+
+**Step 3: Verify the change is correct**
+
+Visual confirmation that line 485 now shows `cron: 30 8 * * 1`.
+
+---
+
+## Task 2: Regenerate Prow job configurations
+
+**Files:**
+- Regenerated: `ci-operator/jobs/openshift/hypershift/openshift-hypershift-main-periodics.yaml`
+
+**Step 1: Run make update**
+
+Run: `make update`
+
+This command will:
+1. Validate the config changes
+2. Regenerate the Prow job YAML in `ci-operator/jobs/`
+3. Update any metadata files
+
+**Step 2: Verify the generated job file**
+
+Read `ci-operator/jobs/openshift/hypershift/openshift-hypershift-main-periodics.yaml` and confirm the `periodic-ci-openshift-hypershift-main-periodic-jira-agent` job now has `cron: 30 8 * * 1` (around line 152).
+
+---
+
+## Task 3: Update the PR description
+
+**Files:**
+- None (PR metadata only)
+
+**Step 1: Draft PR description**
+
+Create a PR with the following description:
+
+```markdown
+## Summary
+- Updates the HyperShift Jira Agent periodic job schedule from daily (9:00 AM UTC) to weekly (Mondays at 8:30 AM UTC)
+
+## Test plan
+- [ ] Verify CI config syntax is valid (`make update` succeeds)
+- [ ] Confirm generated Prow job has correct cron schedule
+- [ ] Job will run on next Monday at 8:30 AM UTC after merge
+
+🤖 Generated with [Claude Code](https://claude.com/claude-code)
+```
+
+**Step 2: Commit the changes**
+
+```bash
+git add ci-operator/config/openshift/hypershift/openshift-hypershift-main.yaml
+git add ci-operator/jobs/openshift/hypershift/openshift-hypershift-main-periodics.yaml
+git commit -m "feat(hypershift): update jira-agent to run weekly on Mondays at 8:30 UTC
+
+Co-Authored-By: Claude Opus 4.5 "
+```
+
+**Step 3: Create the PR**
+
+```bash
+gh pr create --title "Update HyperShift Jira Agent schedule to weekly Monday 8:30 UTC" --body "$(cat <<'EOF'
+## Summary
+- Updates the HyperShift Jira Agent periodic job schedule from daily (9:00 AM UTC) to weekly (Mondays at 8:30 AM UTC)
+
+## Test plan
+- [ ] Verify CI config syntax is valid (`make update` succeeds)
+- [ ] Confirm generated Prow job has correct cron schedule
+- [ ] Job will run on next Monday at 8:30 AM UTC after merge
+
+🤖 Generated with [Claude Code](https://claude.com/claude-code)
+EOF
+)"
+```
+
+---
+
+## Summary of Changes
+
+| File | Change |
+|------|--------|
+| `ci-operator/config/openshift/hypershift/openshift-hypershift-main.yaml` | Change `cron: 0 9 * * *` to `cron: 30 8 * * 1` |
+| `ci-operator/jobs/openshift/hypershift/openshift-hypershift-main-periodics.yaml` | Auto-regenerated by `make update` |