# jenkins-job-insight

> Turn failing Jenkins jobs into clear answers, collaborative reviews, and ready-to-file bugs.

---

Source: analyze-your-first-jenkins-job.md

# Analyze Your First Jenkins Job

You want JJI running, pointed at one real Jenkins failure, and showing a report you can use instead of digging through raw Jenkins output. The fastest first run uses Docker Compose for the server, then `jji` from the checkout to queue one build and open the stored result.

## Prerequisites
- Docker with Docker Compose, or Python 3.12 plus Node.js, npm, and `uv` if you want the local-install path
- A Jenkins URL, username, password or API token, and a build number you want to inspect
- One AI provider credential. The smallest first setup is `claude` with `ANTHROPIC_API_KEY`

## Quick Example

```bash
cp .env.example .env
```

```dotenv
JENKINS_URL=https://jenkins.example.com
JENKINS_USER=your-username
JENKINS_PASSWORD=your-api-token
AI_PROVIDER=claude
AI_MODEL=your-model-name
ANTHROPIC_API_KEY=your-anthropic-api-key
```

```bash
docker compose up -d
uv run jji --server http://localhost:8000 --user jdoe health
uv run jji --server http://localhost:8000 --user jdoe analyze --job-name my-job --build-number 42
```

```text
Job queued: <job_id>
Status: queued
Poll: /results/<job_id>
```

Open `http://localhost:8000`, save the same username (`jdoe`), then paste the printed `Poll:` path after that host. If the build is still finishing or the analysis is still running, JJI shows the live status page first and switches to the report when it completes.

## Step-by-Step

1. Create the server environment file.

```bash
cp .env.example .env
```

```dotenv
JENKINS_URL=https://jenkins.example.com
JENKINS_USER=your-username
JENKINS_PASSWORD=your-api-token
AI_PROVIDER=claude
AI_MODEL=your-model-name
ANTHROPIC_API_KEY=your-anthropic-api-key
```

Replace the placeholders with real values. For the first run, keep everything else at the defaults from `.env.example`.

> **Tip:** Use a failed build that has already finished. You will get to the report faster than waiting for Jenkins to complete a running job.

2. Start JJI and confirm the server is reachable.

```bash
docker compose up -d
uv run jji --server http://localhost:8000 --user jdoe health
```

When the container is ready, the health command should report `healthy`. The same server at `http://localhost:8000` serves the web UI and the API.

3. Save a browser username.

Open `http://localhost:8000/`. On the first visit, JJI sends you to `/register`.

Enter the same username you used with `--user` and click **Save**. Leave `API Key`, `GitHub Token`, `Jira Email`, and `Jira Token` empty for this first run.

> **Note:** A username is enough to analyze a job and open the report. Admin access and personal tracker tokens are only needed for later features.

4. Queue the Jenkins build.

```bash
uv run jji --server http://localhost:8000 --user jdoe analyze --job-name my-job --build-number 42
```

Replace `my-job` and `42` with a real Jenkins job and build number. If the job lives inside Jenkins folders, keep the full Jenkins path style, such as `folder/job-name`.

The command prints a new `job_id` plus a `Poll:` path. JJI stores the run immediately, then moves it through `waiting`, `pending`, or `running` until the report is ready.

5. Open the first report.

```bash
uv run jji --server http://localhost:8000 --user jdoe status <job_id>
uv run jji --server http://localhost:8000 --user jdoe results show <job_id>
```

Use `status` while the run is in progress. Then either open `http://localhost:8000` plus the `Poll:` path, or click the new row on the dashboard.

The dashboard opens the live status page for in-progress runs and the final report for completed runs. The status page refreshes every 10 seconds and automatically switches to the report page, where you will see the Jenkins link, failure count, AI provider/model, a key takeaway, and the grouped failures from the build.

## Advanced Usage

### Run from a local install instead of Docker

```bash
uv sync

cd frontend
npm ci --no-audit --no-fund
npx vite build
cd ..

export AI_PROVIDER=claude
export AI_MODEL=your-model-name
export ANTHROPIC_API_KEY=your-anthropic-api-key

uv run jenkins-job-insight
```

Reuse the same `.env` file from the Docker steps for `JENKINS_*` and other server settings. For a local process, export `AI_PROVIDER`, `AI_MODEL`, and the matching provider credential before you start the server.

> **Warning:** If a local run says no AI provider or model is configured even though those values are in `.env`, export them in your shell before starting `uv run jenkins-job-insight`.

### Choose a different AI provider

| Provider | Minimum settings |
| --- | --- |
| `claude` | `AI_PROVIDER=claude`, `AI_MODEL=opus-4`, `ANTHROPIC_API_KEY=...` |
| `gemini` | `AI_PROVIDER=gemini`, `AI_MODEL=gemini-2.5-pro`, `GEMINI_API_KEY=...` |
| `cursor` | `AI_PROVIDER=cursor`, `AI_MODEL=gpt-5.4-xhigh`, `CURSOR_API_KEY=...` |

The Docker image already includes the supported provider CLIs. You only need to supply the matching credentials.

> **Tip:** `gemini` can also use `gemini auth login` instead of `GEMINI_API_KEY`.

### Save CLI defaults so you can skip repeated flags

```toml
[default]
server = "local"

[servers.local]
url = "http://localhost:8000"
username = "jdoe"
```

Save that as `~/.config/jji/config.toml`. After that, `uv run jji health` and `uv run jji analyze --job-name my-job --build-number 42` can use the saved server and username automatically.

### Make the `Poll:` line use a full URL

```dotenv
PUBLIC_BASE_URL=https://jji.example.com
```

Without `PUBLIC_BASE_URL`, JJI returns relative links such as `/results/<job_id>`. Set it when you want the CLI output and generated report links to point at the public hostname directly.

- See [Configuration and Environment Reference](configuration-and-environment-reference.html) for every server setting.
- See [CLI Command Reference](cli-command-reference.html) for all `jji` commands.
- See [Copy Common Analysis Recipes](copy-common-analysis-recipes.html) for ready-made commands such as `--no-wait`, self-signed Jenkins, forced analysis, and extra repository context.
- See [Copy Common Deployment Recipes](copy-common-deployment-recipes.html) for container and reverse-proxy patterns.

## Troubleshooting

- `Error: No server specified`: pass `--server http://localhost:8000`, or save a CLI profile in `~/.config/jji/config.toml`.
- The browser keeps sending you to `/register`: save a username first. A normal first run does not need an API key.
- The local install shows `Frontend not built`: rebuild the frontend with `npm ci --no-audit --no-fund` and `npx vite build` in `frontend/`, then restart the server.
- Jenkins TLS errors block the first run: set `JENKINS_SSL_VERIFY=false` in `.env`, or use the self-signed Jenkins recipe in [Copy Common Analysis Recipes](copy-common-analysis-recipes.html).
- The run stays in `waiting`: JJI is still polling Jenkins for build completion. Use a finished build for the fastest first run, or see [Copy Common Analysis Recipes](copy-common-analysis-recipes.html) for a no-wait example.
- The `Poll:` line is only `/results/<job_id>`: that is expected until `PUBLIC_BASE_URL` is set.

## Related Pages

- [Analyze a Jenkins Job](analyze-a-jenkins-job.html)
- [Review and Classify Failures](review-and-classify-failures.html)
- [Copy Common Analysis Recipes](copy-common-analysis-recipes.html)
- [Copy Common Deployment Recipes](copy-common-deployment-recipes.html)
- [Configuration and Environment Reference](configuration-and-environment-reference.html)

---

Source: configure-your-profile-and-notifications.md

# Configure Your Profile and Notifications

Set the `Username` JJI should use for you, save your own GitHub or Jira credentials, and turn on browser alerts for `@mentions`. This keeps follow-up work tied to your account and helps you catch comment pings without watching the page.

## Prerequisites
- Access to the JJI web UI.
- A GitHub personal access token with `repo` scope if you want GitHub issue creation from the web UI.
- For Jira Cloud, your Atlassian email plus an API token. For Jira Server/Data Center, a personal access token.
- A browser that supports notifications if you want `@mention` alerts.

## Quick Example
```bash
jji validate-token github --token "<your-github-token>"
```

If the command prints `Valid`, open `/register`, enter a `Username`, paste the token into `GitHub Token`, and click **Save**. After you land on the dashboard, accept the notification prompt, or open `/settings` later and enable notifications there.

> **Tip:** Choose a simple username like `jdoe`, `jdoe-dev`, or `jdoe_1` if you want reliable `@mentions`. Email-style names do not work well with mentions.

## Step-by-Step
1. Open your profile page.

   Use `/register` the first time. After that, click the settings icon in the user badge and open `/settings`.

> **Note:** `API Key` is separate from your GitHub and Jira tokens. Leave it empty unless your administrator gave you an admin key.

2. Enter your `Username`.

   JJI uses this name for comments, the `Mentions` list, and the attribution it adds when it creates a GitHub issue or Jira bug for you.

3. Fill in only the tracker fields you need.

| You want to do | Fill in | What to use |
| --- | --- | --- |
| Create GitHub issues from the web UI | `GitHub Token` | GitHub personal access token with `repo` scope |
| Create Jira bugs on Jira Cloud | `Jira Email` and `Jira Token` | Your Atlassian account email and API token |
| Create Jira bugs on Jira Server/Data Center | `Jira Token` | A personal access token, with `Jira Email` left blank |

4. Click **Save**.

   JJI validates new or changed tracker tokens when you save. When a token checks out, the form shows `Authenticated as ...`.

> **Note:** JJI saves tracker tokens in the current browser and also syncs them to the server, encrypted at rest, so they can be restored when you use the same username in another browser.

5. Enable browser notifications.

   After you already have a username, JJI can show an `Enable Notifications?` prompt on the dashboard. If you skip it, open `/settings` and use the `Push Notifications` section to enable or disable notifications manually.

6. Check your mentions.

   When someone mentions your username in a comment, JJI can show a browser notification and increments the unread badge on `Mentions`. Open `Mentions` to review the list and mark items as read.

## Advanced Usage
```bash
jji validate-token jira --token "<your-jira-token>" --email "you@example.com"
jji validate-token jira --token "<your-server-or-dc-token>"
jji --json validate-token github --token "<your-github-token>"
```

Use `--email` for Jira Cloud and leave it out for Jira Server/Data Center. Add `--json` when you want machine-readable output. See [CLI Command Reference](cli-command-reference.html) for more command examples.

| Setting | Shared across browsers? | Notes |
| --- | --- | --- |
| Saved GitHub or Jira tokens | Yes, when you use the same username | JJI can restore the saved server copy for that username into another browser. |
| Notification permission and push subscription | No | Enable notifications separately in each browser or device. |
| Automatic dashboard prompt | No | Clicking `Not now` is remembered only in the current browser. |

Saving only one tracker field later does not erase the other saved tracker fields. Self-mentions still appear in `Mentions`, but they do not trigger a push notification.

For automation outside the web UI, see [REST API Reference](rest-api-reference.html).

## Troubleshooting
- **`The username 'admin' is reserved`:** use a different username unless you have an admin API key.
- **GitHub or Jira validation says the token is invalid:** generate a fresh token and try again. For Jira Cloud, make sure `Jira Email` matches the same Atlassian account as the token.
- **You never see a notification prompt:** open `/settings` and check `Push Notifications` directly. The automatic prompt only appears once per browser, and only when browser notifications are available.
- **Enabling notifications says they are not configured on the server:** browser notifications are not enabled on this JJI server yet. Ask your administrator to check the notification setup; see [Configuration and Environment Reference](configuration-and-environment-reference.html).
- **Notifications are blocked:** re-enable this site's notification permission in your browser settings, then try again from `/settings`.
- **Notifications fail in Brave with a push service error:** turn on `Use Google services for push messaging` in Brave, then retry.
- **Saved tokens do not show up in another browser:** make sure you used the same username there. Saved tracker credentials are tied to the username you chose.
- **People cannot `@mention` you reliably:** switch to a simple username such as `jdoe`, `jdoe-dev`, or `jdoe_1` instead of an email-style name.

## Related Pages

- [Review and Classify Failures](review-and-classify-failures.html)
- [Create GitHub Issues and Jira Bugs](create-github-issues-and-jira-bugs.html)
- [CLI Command Reference](cli-command-reference.html)
- [REST API Reference](rest-api-reference.html)
- [Configuration and Environment Reference](configuration-and-environment-reference.html)

---

Source: analyze-a-jenkins-job.md

# Analyze a Jenkins Job

Run analysis against a Jenkins build, follow it from queue to report, and start another pass when you need updated output or different AI settings. This gets you from a job name and build number to a usable result quickly, without losing the original run when you retry.

> **Note:** The web UI uses the dashboard to monitor and reopen analyses. New Jenkins analyses are submitted through `jji` or the REST API, then tracked from the dashboard and live status view.

## Prerequisites
- A reachable JJI server.
- A Jenkins job name and build number.
- Jenkins access and an AI provider/model already configured on the server, or passed on the request.
- For the browser, a username in the web app if prompted.
- For `jji`, either pass `--server` and `--user`, or use a default CLI server profile.
- If your server uses an allow list, use a permitted username.

## Quick Example

```bash
jji --server http://localhost:8000 --user alice analyze \
  --job-name "my-job" \
  --build-number 42 \
  --provider claude \
  --model opus

jji --server http://localhost:8000 --user alice status <job-id>

jji --server http://localhost:8000 --user alice re-analyze <job-id>
```

The first command queues the analysis and prints a new `job_id` plus a poll link. Use that `job_id` in the next commands to watch the run or queue another analysis of the same Jenkins build.

> **Tip:** If you already set a default CLI server profile, you can usually omit `--server` and `--user`. See [Configuration and Environment Reference](configuration-and-environment-reference.html) for details.

## Step-by-Step
1. Queue the analysis.

```bash
jji --server http://localhost:8000 --user alice analyze \
  --job-name "my-job" \
  --build-number 42 \
  --provider claude \
  --model opus
```

JJI returns immediately with `queued`, a new `job_id`, and a `result_url`. Keep the `job_id`; you will use it to watch the run and open the finished report.

2. Watch the run from the status view or dashboard.

```bash
jji --server http://localhost:8000 --user alice status <job-id>
```

Open the dashboard in your browser to watch the same run live. Active rows open the status view, and both the dashboard and status view refresh automatically.

| Status | What it means | What to do |
| --- | --- | --- |
| `queued` | JJI accepted the request | Save the `job_id` |
| `pending` | The analysis is waiting in JJI's queue | Keep watching |
| `waiting` | JJI is waiting for Jenkins to finish the build | Let the build finish, or change wait settings on a new submission |
| `running` | AI analysis is in progress | Stay on the status view |
| `completed` | The report is ready | Open the result |
| `failed` | The run stopped before completing | Read the error and decide whether to rerun |
| `Timed Out` | The web UI recognized an AI timeout | Rerun with a longer AI timeout or a different model |

> **Note:** `Timed Out` is a web UI label for AI-analysis timeouts. The API and CLI still report the underlying run as `failed`.

3. Open the finished result.

```bash
jji --server http://localhost:8000 --user alice results show <job-id>
```

In the browser, click the completed row in the dashboard. If you open the returned `result_url` before the run finishes, JJI redirects you to the live status view automatically.

See [Investigate Failure History](investigate-failure-history.html) for details if you need to compare recurring failures across builds. See [Create GitHub Issues and Jira Bugs](create-github-issues-and-jira-bugs.html) for details if the result should become a ticket.

4. Run it again when you need another pass.

```bash
jji --server http://localhost:8000 --user alice re-analyze <job-id>
```

Use this when you want the fastest retry with the same saved settings. JJI creates a new analysis record and a new `job_id`, but it keeps the same Jenkins job and build number.

Use the `Re-Analyze` button on a failed status page or a completed report when you need to change AI settings before retrying. That path can override provider/model, AI timeout, prompt, repo context, Jira settings, and peer-review settings.

> **Warning:** `Re-Analyze` does not trigger a new Jenkins build. If you need fresh Jenkins output, rerun the job in Jenkins first and then submit the new build number with `jji analyze` or `POST /analyze`.

## Advanced Usage
### Submit Through the REST API

```bash
curl -sS -X POST "http://localhost:8000/analyze" \
  -H "Content-Type: application/json" \
  -H "Cookie: jji_username=alice" \
  -d '{"job_name":"my-job","build_number":42,"ai_provider":"claude","ai_model":"opus"}'
```

Poll the returned job with `GET /results/<job-id>` until it reaches `completed` or `failed`. While a run is still active, `GET /results/<job-id>` returns HTTP `202`; once it finishes, it returns HTTP `200`.

### Re-Analyze Through the REST API With Overrides

```bash
curl -sS -X POST "http://localhost:8000/re-analyze/<job-id>" \
  -H "Content-Type: application/json" \
  -H "Cookie: jji_username=alice" \
  -d '{"ai_provider":"claude","ai_model":"opus","ai_cli_timeout":20}'
```

Use this when the quick CLI rerun is too limited. The rerun keeps the original Jenkins job and build number, but lets you override the shared analysis fields that the endpoint accepts.

### Pick the Right Rerun Path

| If you need to... | Use |
| --- | --- |
| Retry the same Jenkins build with the same saved settings | `jji re-analyze <job-id>` |
| Retry the same Jenkins build with different AI settings | The `Re-Analyze` button or `POST /re-analyze/<job-id>` |
| Analyze a newer Jenkins build | A fresh `jji analyze` or `POST /analyze` request with the new build number |
| Change Jenkins wait behavior | A fresh `jji analyze` or `POST /analyze` request |
| Script against the full status payload | `jji --json status <job-id>` |

### Useful `jji analyze` Flags

| Need | Option |
| --- | --- |
| Skip waiting for Jenkins and queue the analysis immediately | `--no-wait` |
| Wait for Jenkins, but poll less often | `--poll-interval 5` |
| Stop waiting after a fixed amount of time | `--max-wait 30` |
| Analyze a build even if Jenkins marked it successful | `--force` |

The live status view can show more than one phase on longer runs, including waiting for Jenkins, analyzing test failures, analyzing child jobs, Jira enrichment, peer-review rounds, and the final save step. The progress log is persisted, so refreshing the page does not erase what has already happened.

See [CLI Command Reference](cli-command-reference.html) for details on every command and flag. See [REST API Reference](rest-api-reference.html) for details on every request field and status code. See [Configuration and Environment Reference](configuration-and-environment-reference.html) for details on reusable CLI server profiles and server defaults.

## Troubleshooting
- You cannot see `Re-Analyze`: wait for the run to fail or complete. Active runs stay on the live status flow.
- `400` when you submit a run usually means the request is missing required fields such as `job_name`, `build_number`, `ai_provider`, or `ai_model`, or the build number is not numeric.
- `403` from the API or `Access denied` in the browser usually means your user is not allowed to use this server or view that job.
- `Job not found` usually means the `job_id` is wrong or the analysis was deleted.
- `Timed Out` in the web UI means the AI analysis timed out. Retry with a longer AI timeout or a different model.
- After a server restart, jobs that were still `waiting` resume automatically. Jobs that were already `pending` or `running` should be requeued.

## Related Pages

- [Analyze Your First Jenkins Job](analyze-your-first-jenkins-job.html)
- [Customize AI Analysis](customize-ai-analysis.html)
- [Review and Classify Failures](review-and-classify-failures.html)
- [Investigate Failure History](investigate-failure-history.html)
- [Create GitHub Issues and Jira Bugs](create-github-issues-and-jira-bugs.html)

---

Source: review-and-classify-failures.md

# Review and Classify Failures

Use the report page to turn an analysis into an actionable team record: confirm what is understood, call in the right person, and correct the AI when needed. The goal is to leave each failure card with a clear review state, useful comments, and the right manual classification.

## Prerequisites
- A completed analysis report. If you need one first, see [Analyze Your First Jenkins Job](analyze-your-first-jenkins-job.html).
- A saved username in the web UI so comments, reviews, and mentions are attributed correctly.

## Quick Example

```text
Hey @alice, can you check this?
```

1. Open a completed report and expand a card that shows `+N more with same error`.
2. Read the `Error` and `Analysis` sections.
3. Paste the comment above into `Add a comment...`, then click `Post`.
4. Click `Review All` for the whole group, or `Review` if the card contains only one test.
5. If the failure belongs to the environment rather than the code or product, choose `INFRASTRUCTURE` from the classification dropdown.

> **Tip:** `Shift+Enter` adds a new line. If the mention picker is open, `Enter` or `Tab` inserts the highlighted username instead of posting.

## Step-by-Step
1. Start with grouped failures. In the `Failures` section, a card with `+N more with same error` means multiple tests are being worked as one review unit. Expand the card to see the affected tests, the raw `Error`, the AI `Analysis`, and any evidence or fix details.

2. Mark items as reviewed as you finish them. Single-test cards use `Review`. Grouped cards add `Review All`, and you can still mark individual tests inside `Affected Tests` if only part of the group is ready. When a card is marked, the button shows who reviewed it.

3. Leave comments in the card where the decision belongs. Use `Add a comment...` and `Post` at the bottom of the expanded card. Comments save immediately, show the author and timestamp, and stay attached to that failure card scope. The trash icon only appears for comments posted under your current username.

4. Use `@mentions` when you need a handoff or second opinion. Type `@` and start a username to open the suggestion list. `Enter` or `Tab` inserts the selected name, and `Escape` closes the picker. Mentions are only recognized as standalone names, so `user@domain.com` does not count as a mention.

```text
@alice
@my-user
@another_user
@user123
```

5. Override the AI classification when human judgment should win. The classification dropdown is intentionally limited to `CODE ISSUE`, `PRODUCT BUG`, and `INFRASTRUCTURE`. On a grouped card, the change applies to every test shown in that group within the current scope.

| Override | Use it when | What changes in the card |
| --- | --- | --- |
| `CODE ISSUE` | The failure should be fixed in the product code or the tests | `Bug Report` is removed, and any `Suggested Fix` stays visible |
| `PRODUCT BUG` | The product behavior is the real defect | `Suggested Fix` is removed, and any `Bug Report` stays visible |
| `INFRASTRUCTURE` | Jenkins, the lab, credentials, or another external dependency caused the failure | Both `Suggested Fix` and `Bug Report` are removed |

6. Review nested child jobs in place. If the report has a `Child Jobs` section, expand the failing child build and keep drilling down until you reach the exact branch of the pipeline you need. Reviews, comments, and overrides are scoped to that child build, so the same test name in another child build is handled separately.

7. Check progress in the sticky header and move on. The header shows whether nothing, some, or all failures are reviewed. Comment and review changes appear automatically while the report is open. Manual classification overrides save immediately, but other people with the same report already open need to reload to see them.

> **Note:** There is no final save step. Review toggles, comments, and classification overrides are stored as soon as you make the change.

## Advanced Usage
Use `Expand all` and `Collapse all` in both `Failures` and `Child Jobs` when you are working through a long report. This is the fastest way to compare repeated failures or walk a multi-level pipeline without reopening cards one by one.

If a grouped card shows more than one classification badge, the tests inside that group already have mixed saved overrides. Picking one value in the classification dropdown normalizes the entire group again in that scope.

Paste tracker references directly into comments when you want the card to carry follow-up status. JJI recognizes full GitHub issue URLs, full GitHub pull request URLs, and Jira keys.

```text
Fix merged: https://github.com/org/repo/pull/123
See https://github.com/RedHatQE/mtv-api-tests/issues/359
Opened bug: OCPBUGS-12345
```

When JJI can resolve those references, it adds live status badges such as `merged`, `open`, or `closed` next to the link or ticket key. While you are typing a draft comment, automatic comment and review refresh pauses so your text is not interrupted.

Use the copy buttons beside the test name, error, analysis, and other card sections when you need to paste evidence into chat, tickets, or commit messages. For scripted review workflows instead of the browser, see [CLI Command Reference](cli-command-reference.html) or [REST API Reference](rest-api-reference.html).

## Troubleshooting
- I can open the report, but posting comments, marking review, or changing the classification fails: your server may be using a write allow list, or your username is missing. Ask an administrator to add your username if needed.
- I do not see the trash icon for a comment I wrote earlier: switch back to the same username that was active when you posted it.
- A teammate's new comment or review is not showing up yet: wait until you are not typing in a comment box, or reload the report.
- A teammate's manual override is not showing up yet: reload the report. Manual override changes are not live-polled into other open tabs.
- My `@mention` did not trigger: use a standalone token such as `@alice`, not an email address like `user@domain.com`.
- Reclassifying one child build did not change another one with the same test name: repeat the change inside each child build you want to update.
- A grouped `Review All` or classification change only partly succeeded: retry from the same card. JJI keeps the successful updates and names the tests that still failed.
- A tracker link has no status badge: use a full GitHub issue or pull-request URL, or a Jira key such as `OCPBUGS-12345`, and make sure the server can reach that tracker.

## Related Pages

- [Analyze a Jenkins Job](analyze-a-jenkins-job.html)
- [Investigate Failure History](investigate-failure-history.html)
- [Create GitHub Issues and Jira Bugs](create-github-issues-and-jira-bugs.html)
- [Push Classifications to Report Portal](push-classifications-to-report-portal.html)
- [Configure Your Profile and Notifications](configure-your-profile-and-notifications.html)

---

Source: investigate-failure-history.md

# Investigate Failure History

Use failure history when you want to answer "have we seen this before?" before you reclassify a failure or file a bug. Looking across earlier runs helps you separate recurring noise from a real new breakage.

## Prerequisites

- At least one completed analysis in JJI. See [Analyze a Jenkins Job](analyze-a-jenkins-job.html) for details.
- Access to the web app or a configured `jji` CLI. See [CLI Command Reference](cli-command-reference.html) for details.
- A browser username if you want to use the web UI. See [Configure Your Profile and Notifications](configure-your-profile-and-notifications.html) for details.

## Quick Example

```bash
jji history failures
```

Start here when you want the newest recorded failures across all analyzed jobs, then narrow the list by test, classification, job, or signature.

## Step-by-step

> **Tip:** Use the browser to browse and open results quickly. Switch to `jji` when you need an exact job filter, signature search, or JSON output.

| You want to know... | Fastest path |
| --- | --- |
| What has been failing lately? | `History` in the web app or `jji history failures` |
| How often does one test fail in one job? | `jji history test TEST_NAME --job-name JOB_NAME` |
| Do several tests share one failure pattern? | `jji history search --signature SIGNATURE` |
| Is one Jenkins job getting noisier overall? | `jji history stats JOB_NAME` |

1. Start with the broad list.

   ```bash
   jji history failures --limit 50
   ```

   In the browser, open `History` to scan the latest failures. Use the search box to narrow the list, use the classification picker to focus on one category, click a row to open the full result, or click the test name to open that test's history page.

2. Drill into one test.

   ```bash
   jji history test tests.network.TestDNS.test_lookup --job-name ocp-4.16-e2e
   ```

   Use this when one failure keeps reappearing and you want to know whether it is specific to one job. The test view shows recent runs, the latest classification, related comments, and timestamps such as first seen and last seen.

> **Note:** If you want pass counts or a failure rate, include `--job-name`. Without a job filter, the single-test view can still show failure history, but it may not have enough context to calculate a full pass/fail rate.

3. Check whether the same root cause hits multiple tests.

   ```bash
   jji history search --signature sig-abc
   ```

   Run this when several failures look related and you already have a signature value. The default output shows how many times that signature appeared and which tests share it, so you can tell whether you are looking at one repeated failure mode or several unrelated problems.

4. Measure job-wide noise before you decide it is a regression.

   ```bash
   jji history stats ocp-e2e
   ```

   This gives you the number of analyzed builds, how many of them had failures, and the most common failing tests for that job. Use it when the question is "is this job unhealthy lately?" rather than "what happened to this one test?"

5. Narrow the list to what you actually need to triage.

   ```bash
   jji history failures --job-name ocp-e2e --classification "REGRESSION"
   ```

   This is a good last step before taking action. If the pattern looks stable and already explained, continue with [Review and Classify Failures](review-and-classify-failures.html); if it points to a new defect, continue with [Create GitHub Issues and Jira Bugs](create-github-issues-and-jira-bugs.html).

## Advanced Usage

```bash
jji history failures --search "DNS resolution failed"
```

Use `--search` when you only have a fragment of the failure in front of you. It is the quickest way to get back to the right test or result before you decide whether you need a deeper job or signature lookup.

```bash
jji history failures --job-name ocp-e2e --classification "FLAKY" --limit 50 --offset 50
```

Combine exact job filtering, classification filtering, and pagination when the browser list is too broad. The classification filter accepts `CODE ISSUE`, `PRODUCT BUG`, `FLAKY`, `REGRESSION`, `INFRASTRUCTURE`, `KNOWN_BUG`, and `INTERMITTENT`.

```bash
jji history test tests.TestA.test_one --job-name ocp-e2e --exclude-job-id job-99
jji history search --signature sig-abc --exclude-job-id job-99
jji history stats ocp-e2e --exclude-job-id job-99
```

Use `--exclude-job-id` when you are triaging one active result and want to compare it against older history without counting the current run. This makes it easier to decide whether the current failure is genuinely new.

```bash
jji --json history stats ocp-e2e
jji --json history search --signature sig-abc
```

Use JSON output for scripting, dashboards, or when you want fields that are not shown in the default table view. For example, `jji --json history stats` includes the recent trend value for the job. See [REST API Reference](rest-api-reference.html) if you want to automate the same workflows over HTTP.

> **Tip:** In the browser, you can sort the current page by test, job, classification, or date. Use pagination to move through older results.

## Troubleshooting

- **`Failure rate` shows `N/A`, or the per-test totals do not look useful.** Run `jji history test TEST_NAME --job-name JOB_NAME` so JJI can estimate pass/fail math for one job.
- **Signature search returns nothing.** Make sure you are using the signature value, not the human-readable error message. If all you have is the error text, start with `jji history failures --search "..."`.
- **The CLI says no server was specified.** Add `--server`, set `JJI_SERVER`, or configure a default CLI profile. See [CLI Command Reference](cli-command-reference.html) for details.
- **History looks empty right after you start a new analysis.** Wait for the analysis to complete, then try again. If you do not have any completed analyses yet, start with [Analyze a Jenkins Job](analyze-a-jenkins-job.html).

## Related Pages

- [Review and Classify Failures](review-and-classify-failures.html)
- [Analyze a Jenkins Job](analyze-a-jenkins-job.html)
- [Create GitHub Issues and Jira Bugs](create-github-issues-and-jira-bugs.html)
- [CLI Command Reference](cli-command-reference.html)
- [REST API Reference](rest-api-reference.html)

---

Source: customize-ai-analysis.md

# Customize AI Analysis

You want the AI to explain a failure with the right model, the right code context, and the right amount of debate. JJI lets you keep sensible defaults and then override them for one run or one rerun when a job needs better guidance.

## Prerequisites
- A JJI server or CLI profile you can submit jobs to.
- Jenkins access for the job you want to inspect.
- An authenticated AI CLI for `claude`, `gemini`, or `cursor`.

## Quick Example

```bash
jji analyze \
  --job-name my-job \
  --build-number 27 \
  --provider claude \
  --model opus-4
```

Use this when you want a single analysis with an explicit provider and model, without changing any saved defaults.

## Step-by-Step

1. Save the defaults you use most.

```toml
[default]
server = "prod"

[defaults]
ai_provider = "claude"
ai_model = "opus-4"
ai_cli_timeout = 10
tests_repo_url = "https://github.com/your-org/your-tests"
wait_for_completion = true
poll_interval_minutes = 2
max_wait_minutes = 0

[servers.prod]
url = "https://jji.example.com"
username = "jdoe"
```

Place this in `~/.config/jji/config.toml` when you want your everyday provider, model, repo, and wait settings to follow you. CLI flags still override these values for one-off runs.

2. Switch provider or model for one job.

```bash
jji analyze \
  --job-name my-job \
  --build-number 27 \
  --provider cursor \
  --model gpt-5.4-xhigh
```

This is the fastest way to compare models on the same kind of failure. It changes only this run.

3. Add peer reviewers when one model is not enough.

```bash
jji analyze \
  --job-name my-job \
  --build-number 27 \
  --provider claude \
  --model opus-4 \
  --peers "cursor:gpt-5.4-xhigh,gemini:gemini-2.5-pro" \
  --peer-analysis-max-rounds 5
```

Each peer reviews the main model’s answer, and JJI can run multiple debate rounds before it returns the final analysis. Keep rounds low unless the failure is genuinely ambiguous.

4. Point JJI at the right repositories.

```bash
jji analyze \
  --job-name my-job \
  --build-number 27 \
  --provider claude \
  --model opus-4 \
  --tests-repo-url https://github.com/org/tests:feature/bar \
  --additional-repos "infra:https://github.com/org/infra:develop,product:https://github.com/org/product"
```

Use `--tests-repo-url` for the main test repo, including a branch or tag with `:ref`. Use `--additional-repos` when the explanation depends on code outside that repo, such as product or infra changes.

> **Tip:** Start with only the repositories that matter to the failure. Adding every nearby repo usually makes the run slower without improving the answer.

5. Add run-specific instructions.

```bash
jji analyze \
  --job-name my-job \
  --build-number 27 \
  --provider claude \
  --model opus-4 \
  --raw-prompt "Focus on network issues and cluster events first."
```

Keep the prompt short and goal-oriented. This works best as a temporary nudge for the current run, not as a permanent place to store team guidance.

6. Control timeouts, artifacts, and wait behavior.

```bash
jji analyze \
  --job-name my-job \
  --build-number 27 \
  --provider claude \
  --model opus-4 \
  --ai-cli-timeout 20 \
  --jenkins-artifacts-max-size-mb 50 \
  --wait \
  --poll-interval 5 \
  --max-wait 30
```

- `--ai-cli-timeout` controls how long each AI CLI call can run.
- `--no-get-job-artifacts` skips artifact downloads entirely when they are slow or noisy.
- `--jenkins-artifacts-max-size-mb` keeps artifact context from getting too large.
- `--wait`, `--poll-interval`, and `--max-wait` control how long JJI waits for Jenkins to finish before analysis starts.

> **Note:** `--max-wait 0` means no limit.

7. Rerun an existing result when you want a quick comparison.

```bash
jji re-analyze old-job-1
```

This reuses the saved settings from the original run. If you want to change provider, peers, repositories, prompt, or artifact settings during a rerun, use the report page’s `Re-Analyze Job` dialog instead. See [Review and Classify Failures](review-and-classify-failures.html) for the report workflow.

## Advanced Usage

Use the smallest scope that solves the problem. Server-wide defaults are best for shared behavior, CLI profile defaults are best for your personal baseline, and per-run overrides are best when only one job needs different treatment.

| What you want to change | One run | CLI profile | Server default |
| --- | --- | --- | --- |
| Main provider and model | `--provider`, `--model` | `ai_provider`, `ai_model` | `AI_PROVIDER`, `AI_MODEL` |
| AI timeout | `--ai-cli-timeout` | `ai_cli_timeout` | `AI_CLI_TIMEOUT` |
| Peer reviewers | `--peers`, `--peer-analysis-max-rounds` | `peers`, `peer_analysis_max_rounds` | `PEER_AI_CONFIGS`, `PEER_ANALYSIS_MAX_ROUNDS` |
| Tests repo | `--tests-repo-url` | `tests_repo_url` | `TESTS_REPO_URL` |
| Extra repos | `--additional-repos` | `additional_repos` | `ADDITIONAL_REPOS` |
| Prompt tweaks | `--raw-prompt`, or the report page’s `Re-Analyze Job` dialog | Not available | Not available |
| Artifact downloads | `--get-job-artifacts`, `--no-get-job-artifacts`, `--jenkins-artifacts-max-size-mb`, or the report page’s `Re-Analyze Job` dialog | Not available | `GET_JOB_ARTIFACTS`, `JENKINS_ARTIFACTS_MAX_SIZE_MB` |
| Jenkins wait behavior | `--wait`, `--no-wait`, `--poll-interval`, `--max-wait` | `wait_for_completion`, `poll_interval_minutes`, `max_wait_minutes` | `WAIT_FOR_COMPLETION`, `POLL_INTERVAL_MINUTES`, `MAX_WAIT_MINUTES` |

> **Note:** CLI flags override `~/.config/jji/config.toml`. See [CLI Command Reference](cli-command-reference.html) for the full flag list and [Configuration and Environment Reference](configuration-and-environment-reference.html) for the full env var list.

### Private Repos and Branch Pins

```bash
jji analyze \
  --job-name my-job \
  --build-number 27 \
  --additional-repos "infra:https://github.com/org/infra:develop@ghp_secret123"
```

Use `name:url:ref@token` when you need both a specific branch and a token for a private repo. Use `https://` for private repos, keep repo names unique, and do not use `build-artifacts` as a repo name.

### Stable Prompts vs One-Off Prompts

If a cloned repo contains `JOB_INSIGHT_PROMPT.md`, JJI can use it as project-specific analysis guidance. Put durable, team-wide instructions there and keep `--raw-prompt` for temporary nudges that only matter to the current run.

### Reruns With Overrides

The browser’s `Re-Analyze Job` dialog can override provider, model, prompt, peers, repositories, and artifact settings before it queues a new analysis. That is also the easiest way to turn peer review off for a single rerun when your saved config already enables peers.

If you need scripted reruns with overrides, use the REST re-analyze endpoint. See [REST API Reference](rest-api-reference.html) for details.

## Troubleshooting

- If JJI says no AI provider or model is configured, either pass `--provider` and `--model` on the command line or set defaults in `config.toml` or the server environment.
- If `--peers` fails, each entry must be `provider:model`, separated by commas. Supported providers are `claude`, `gemini`, and `cursor`.
- If `--additional-repos` fails to parse, use `name:url`, then add optional `:ref` and optional `@token` at the end. Repo names must be unique.
- If a rerun keeps using old peer settings, remember that `jji re-analyze` reuses the saved request. Use the report page’s `Re-Analyze Job` dialog or the REST re-analyze endpoint when you need overrides.
- If runs are slow or noisy, lower the artifact size cap, use `--no-get-job-artifacts`, point `--tests-repo-url` at the correct branch, or tighten `--raw-prompt` so the AI focuses on the right evidence.

## Related Pages

- [Analyze a Jenkins Job](analyze-a-jenkins-job.html)
- [Copy Common Analysis Recipes](copy-common-analysis-recipes.html)
- [Configuration and Environment Reference](configuration-and-environment-reference.html)
- [CLI Command Reference](cli-command-reference.html)
- [REST API Reference](rest-api-reference.html)

---

Source: create-github-issues-and-jira-bugs.md

# Create GitHub Issues and Jira Bugs

You want to turn a failed test into a GitHub issue or Jira bug without rewriting the failure by hand. JJI generates a draft from the analysis, surfaces likely duplicates, and lets you file the ticket in the tracker that owns the problem.

## Prerequisites
- A completed analysis report with the failure you want to file.
- GitHub or Jira ticket creation must be available on your server. Run `jji capabilities` if you want to check before you start. See [Configuration and Environment Reference](configuration-and-environment-reference.html) for server-side setup details.
- In the browser, save a personal tracker token in `Settings`. You can preview a draft without one, but the dialog will not enable `Create` until the matching token is saved.
- For GitHub, have a personal access token with `repo` scope and a repo target.
- For Jira Cloud, have a Jira token, your Jira email, and a project key. For Jira Server/Data Center, use a Jira token and project key.

## Quick Example

```bash
jji preview-issue job-1 \
  --test tests.TestA.test_one \
  --type github \
  --github-token "<your-github-token>" \
  --github-repo-url "https://github.com/org/repo"
```

```bash
jji create-issue job-1 \
  --test tests.TestA.test_one \
  --type github \
  --title "Bug fix" \
  --body "Details..." \
  --github-token "<your-github-token>" \
  --github-repo-url "https://github.com/org/repo"
```

The preview command prints the generated title, body, and any `Similar issues`. The create command returns the new issue URL and the comment id JJI added back on the failure.

> **Tip:** Add `--json` to either command if you want machine-readable output.

The report page uses the same preview-edit-create flow directly from each failure card.

## Step-by-Step
1. Open the completed report and expand the failure you want to file.
2. Click `GitHub Issue` or `Jira Bug` on that failure.
3. Review the generated draft.
   - JJI opens a preview dialog with an editable title and body.
   - Use this step to tighten the summary, remove noise, or add extra context before you submit.
4. Check likely duplicates.
   - Review the `Similar issues` section before you create a new ticket.
   - Each result links to the existing issue and shows its current status when the tracker returns one.
   - An empty list does not block creation.
5. Choose the destination.
   - GitHub: if the analysis used more than one repo, choose the correct repo from the dropdown. If the analysis used only one repo, JJI uses it automatically.
   - Jira: enter or pick the project key that should receive the bug. If your Jira account exposes issue security levels, choose the level you want before you create.
6. Create the ticket.
   - Click `Create GitHub Issue` or `Create Jira Bug`.
   - JJI opens the new ticket and adds a comment on the failure linking back to it.

> **Note:** In the browser, preview works without a personal GitHub or Jira token, but the `Create` button stays disabled until the matching token is saved in `Settings`.


> **Tip:** Treat duplicate matching as a fast sanity check, not a hard blocker. Review the shortlist, then use your judgment.

## Advanced Usage
### Check server support first
```bash
jji capabilities --json
```

Use this when you want to confirm whether GitHub or Jira creation is enabled before you script anything. The JSON output also shows whether the server already has tracker credentials configured.

### Validate tokens and confirm Jira access
```bash
jji validate-token github --token "<your-github-token>"
jji validate-token jira --token "<your-jira-token>" --email "you@example.com"
```

```bash
jji jira-projects --jira-token "<your-jira-token>" --jira-email "you@example.com" --query PROJ
jji jira-security-levels PROJ --jira-token "<your-jira-token>" --jira-email "you@example.com"
```

Use these commands when the browser dialog does not show the Jira project you expect, or when you want to confirm access before filing a bug. For Jira Server/Data Center, omit `--email`.

### Add links or change the AI draft
```bash
jji preview-issue job-1 \
  --test tests.TestA.test_one \
  --type github \
  --include-links \
  --ai-provider claude \
  --ai-model opus-4
```

`--include-links` asks JJI to add Jenkins and analysis links to the draft. If the server does not have a public base URL configured, the draft still works, but the references fall back to plain text instead of clickable links.

If the report page shows AI provider and model selectors next to the issue buttons, you can use those instead of CLI flags to change how the draft is written.

### Create tickets for child-job failures
```bash
jji preview-issue job-1 \
  --test tests.TestA.test_one \
  --type jira \
  --child-job child-runner \
  --child-build 5
```

Use `--child-job` and `--child-build` when the failing test came from a pipeline child job rather than the top-level Jenkins job.

### Reuse CLI defaults or automate the flow
If you create tracker tickets often, store defaults such as `github_token`, `github_repo_url`, `jira_token`, `jira_email`, `jira_project_key`, and `jira_security_level` in your `jji` profile instead of repeating them on every command. See [CLI Command Reference](cli-command-reference.html) for the full option list.

> **Note:** `preview-issue` and `create-issue` only send the credentials for the selected `--type`. GitHub runs ignore Jira credentials, and Jira runs ignore GitHub credentials.

If you need to call the same flow from another tool, see [REST API Reference](rest-api-reference.html).

## Troubleshooting
- The browser dialog will not let me create the ticket: save the matching personal token in `Settings`. Preview still works without it.
- GitHub creation says no repository URL is available: the analysis did not include a repo target, so JJI has nowhere to file the issue. Re-run the analysis with repository context, or use `--github-repo-url` from the CLI. See [Copy Common Analysis Recipes](copy-common-analysis-recipes.html) for working analysis patterns.
- Jira project or issue security choices are missing: validate your Jira token first, and for Jira Cloud include your email. `jji jira-projects` and `jji jira-security-levels` show exactly what your account can access.
- The tracker says your token is invalid or expired: run `jji validate-token` and update the saved token or CLI flag.
- `Similar issues` is empty: duplicate lookup is best effort. You can still review the draft and create the ticket manually.
- The GitHub or Jira button is disabled, or `jji capabilities` shows the tracker as unavailable: the integration is turned off on the server. See [Configuration and Environment Reference](configuration-and-environment-reference.html) for the server-side requirements.

## Related Pages

- [Review and Classify Failures](review-and-classify-failures.html)
- [Configure Your Profile and Notifications](configure-your-profile-and-notifications.html)
- [CLI Command Reference](cli-command-reference.html)
- [REST API Reference](rest-api-reference.html)
- [Configuration and Environment Reference](configuration-and-environment-reference.html)

---

Source: push-classifications-to-report-portal.md

# Push Classifications to Report Portal

You want the classifications already saved in JJI to show up on the matching Report Portal failures so your triage stays consistent without relabeling the same failures twice. This guide shows the fastest push flow and how to interpret launches that only partially matched.

## Prerequisites
- A saved JJI analysis that already contains failures, plus its `job_id`
- Report Portal configured on the JJI server with `REPORTPORTAL_URL`, `REPORTPORTAL_PROJECT`, `REPORTPORTAL_API_TOKEN`, and `PUBLIC_BASE_URL`
- A Report Portal token that can update the target launch
- If your Report Portal uses a self-signed certificate, `REPORTPORTAL_VERIFY_SSL=false`

## Quick example

```bash
jji push-reportportal job-123
```

```text
Pushed 3 classification(s) to Report Portal
Launch ID: 42
```

Run this when the analysis is already saved in JJI. JJI finds the matching Report Portal launch, updates the matched failed items, and prints the launch it touched.

## Step-by-step

1. Configure the server-side Report Portal settings.

```dotenv
REPORTPORTAL_URL=https://reportportal.example.com
REPORTPORTAL_PROJECT=my-project
REPORTPORTAL_API_TOKEN=your-rp-token
PUBLIC_BASE_URL=https://jji.example.com

# Optional for self-signed certificates
REPORTPORTAL_VERIFY_SSL=false
```

`REPORTPORTAL_URL`, `REPORTPORTAL_PROJECT`, and `REPORTPORTAL_API_TOKEN` make the feature available. `PUBLIC_BASE_URL` is required because each push includes a link back to the JJI report.

> **Tip:** You usually do not need `ENABLE_REPORTPORTAL=true`. JJI enables the integration automatically when the URL, project, and token are set. `ENABLE_REPORTPORTAL=false` forces it off.

See [Configuration and Environment Reference](configuration-and-environment-reference.html) for the full setting list.

2. Push the saved analysis from the CLI.

```bash
jji push-reportportal job-123
```

JJI looks up the Report Portal launch by the Jenkins build URL stored with that analysis. It then updates the matched failed items with the JJI classification and a link back to the report.

3. Use the web UI when you prefer point-and-click.

Open the saved report and click **Push to Report Portal**. For reports without child jobs, the button appears in the main report header.

4. Push a specific child job when the analysis contains pipeline or nested child runs.

```bash
jji push-reportportal job-123 \
  --child-job-name "my-child" \
  --child-build-number 42
```

Use the same child job name and build number shown in the JJI report. The same approach works for nested child jobs too.

> **Warning:** `--child-job-name` and `--child-build-number` are a pair. If you provide the child job name, you must also provide the child build number.

In the UI, use the **Push to Report Portal** button on the child job section you want to sync.

5. Switch to JSON output when you need the exact result.

```bash
jji --json push-reportportal job-123
```

Use the returned fields to tell whether the push fully succeeded, partially succeeded, or needs follow-up. See [CLI Command Reference](cli-command-reference.html) for the command variants and global options.

## Advanced Usage

```bash
jji --json push-reportportal job-123
```

| Common outcome | Result pattern | What it means | What to do next |
| --- | --- | --- | --- |
| Successful push | `pushed > 0`, `unmatched = []`, `errors = []` | Everything JJI could update was pushed cleanly. | No follow-up needed. |
| `Some classifications could not be pushed.` | `pushed > 0` with `unmatched` or `errors` | JJI updated some Report Portal items, but not all of them. | Review the unmatched names or errors, fix the cause, then run the same push again. |
| `No classifications could be matched.` | `pushed = 0`, `unmatched` populated, `errors = []` | JJI finished the push attempt, but none of the remaining items could be turned into a Report Portal update. | Check the unmatched names and the classification mapping below. |
| `Failed to push classifications to Report Portal.` | `pushed = 0`, `errors` populated | JJI could not complete the push. | Fix the reported problem, then retry. |

For automation, read the JSON fields instead of relying on the process exit code alone. A push can return structured `unmatched` or `errors` details without turning into a CLI failure.

JJI publishes these classification mappings into Report Portal:

| JJI classification | Sent to Report Portal as |
| --- | --- |
| `PRODUCT BUG` | Product Bug |
| `CODE ISSUE` | Automation Bug |
| `INFRASTRUCTURE` | System Issue |

Matching is tolerant of common naming differences. Exact test-name matches work, and short Report Portal item names can still match fully qualified names from JJI, but unrelated naming schemes remain unmatched.

Only failed Report Portal items are updated. If a failure already has Jira matches attached to a product-bug report, JJI pushes those issue links along with the classification. See [Create GitHub Issues and Jira Bugs](create-github-issues-and-jira-bugs.html) for that workflow.

> **Note:** If a saved history classification marks a test as `INFRASTRUCTURE`, JJI pushes it to Report Portal as a System Issue even when the latest AI classification says something else.

If you prefer a shorter command, `jji push-rp job-123` performs the same push.

## Troubleshooting

- `Report Portal integration is disabled or not configured`: set `REPORTPORTAL_URL`, `REPORTPORTAL_PROJECT`, and `REPORTPORTAL_API_TOKEN`, and make sure `ENABLE_REPORTPORTAL` is not forcing the feature off.
- `PUBLIC_BASE_URL must be set`: point `PUBLIC_BASE_URL` at the public JJI URL users actually open in the browser.
- The **Push to Report Portal** button is missing: the server is not currently exposing Report Portal support, or the report contains child jobs and you need the button inside the child job section instead.
- `Job ... not found`: use the JJI `job_id`, not the Jenkins build number.
- `Child job ... not found`: recheck both the child job name and child build number from the report.
- `No failures to push to Report Portal.`: the selected report or child run has no failures, so JJI stops before contacting Report Portal.
- `No Report Portal launch found`: make sure the matching Report Portal launch description contains the Jenkins build URL from the analyzed run.
- `Ambiguous RP launch`: more than one launch matches the same Jenkins build URL. Remove the duplicate launches, then retry.
- `No failed test items found in RP launch.`: the launch exists, but there were no failed items for JJI to update.
- `No overlap` or many `unmatched` items: keep test names aligned between JJI and Report Portal. Short-name versus fully qualified-name differences are fine; unrelated names are not.
- `403` or `Not a launch owner`: use a Report Portal token that has permission to update that launch.
- TLS or certificate errors: set `REPORTPORTAL_VERIFY_SSL=false` when your Report Portal uses a self-signed certificate.

## Related Pages

- [Review and Classify Failures](review-and-classify-failures.html)
- [Analyze a Jenkins Job](analyze-a-jenkins-job.html)
- [Create GitHub Issues and Jira Bugs](create-github-issues-and-jira-bugs.html)
- [CLI Command Reference](cli-command-reference.html)
- [Configuration and Environment Reference](configuration-and-environment-reference.html)

---

Source: organize-jobs-with-metadata.md

# Organize Jobs with Metadata

Tag jobs with ownership and release context so you can cut a noisy dashboard down to the work your team actually needs to handle. JJI lets you assign metadata manually, seed it in bulk, and preview automatic name-based matches before you rely on them.

## Prerequisites

- At least one analyzed job in JJI.
- Admin access if you want to set, import, or delete metadata.
- A working `jji` CLI profile if you want to use the commands below. See [CLI Command Reference](cli-command-reference.html) for full syntax.
- Access to server configuration if you want automatic rule-based assignment. See [Configuration and Environment Reference](configuration-and-environment-reference.html) for deployment details.

## Quick Example

```bash
jji metadata set "my-job" \
  --team platform \
  --tier critical \
  --version v1.0 \
  --label nightly \
  --label smoke

jji metadata get "my-job"
```

One update tags the Jenkins job itself, not just a single build, so the same metadata shows up on every analyzed run of that job.

## Step-by-Step

1. **Tag one job manually.**

```bash
jji metadata set "my-job" \
  --team platform \
  --tier critical \
  --version v1.0 \
  --label nightly \
  --label smoke
```

Use `jji metadata get "my-job"` to confirm what is stored.

2. **Update only the field that changed.**

```bash
jji metadata set "my-job" --team beta
```

This changes only `team`. Fields you leave out stay unchanged.

> **Tip:** When you pass `--label`, you replace the stored label list for that job. Pass every label you want to keep in the same command.

3. **Import metadata for existing jobs in bulk.**

```json
[
  {"job_name": "job-a", "team": "alpha"},
  {"job_name": "job-b", "team": "beta", "labels": ["ci"]}
]
```

```bash
jji metadata import metadata.json
```

Use bulk import when you already know the mapping and want to tag many existing jobs quickly. The import file can be JSON or YAML.

> **Note:** Import fully rewrites each job entry in the file. Omitted optional fields reset to blank or empty for those jobs.

4. **Configure automatic matching for new analyses.**

```yaml
metadata_rules:
  - pattern: 'test-*'
    team: qa
    labels: [smoke]
  - pattern: 'dev-*'
    labels: [dev]
```

Set the server's `METADATA_RULES_FILE` to your rules file, restart JJI, and then verify what the server loaded:

```bash
jji metadata rules
jji metadata preview "test-something"
```

Use the preview command to check a job name safely before a new analysis writes metadata. See [Configuration and Environment Reference](configuration-and-environment-reference.html) for the server setting.

5. **Let new analyses pick up metadata automatically.**

After the rules file is active, analyze or re-analyze a matching job. See [Analyze a Jenkins Job](analyze-a-jenkins-job.html) for the analysis workflow.

> **Warning:** Automatic rules do not overwrite existing job metadata. Manual or imported values stay in place until you change or delete them.

6. **Filter the dashboard into a smaller triage queue.**

```bash
jji metadata list --team alpha --tier critical
```

This shows only jobs whose stored metadata matches those filters. In the Dashboard, use the Team, Tier, and Version dropdowns plus the label chips to narrow the list, and look for metadata badges beside each job name.

> **Tip:** Dashboard metadata filters stay in the URL, so you can bookmark or share a focused queue for one team, one tier, or one release.

## Advanced Usage

| If you want to... | Use | What happens |
| --- | --- | --- |
| Change one field and keep the rest | `jji metadata set "my-job" --team beta` | Only the fields you pass are updated. |
| Replace metadata for one or many jobs from a file | `jji metadata import metadata.json` | Each imported job is fully rewritten. |
| Test automatic rules without storing anything | `jji metadata preview "test-something"` | JJI shows the match but does not save metadata. |

```bash
jji metadata set "folder/subfolder/my-job" --team platform
```

Quote folder-style job names when they include slashes.

- Rule order matters. If several rules match one job, the first matching rule wins for `team`, `tier`, and `version`.
- Labels accumulate across matching rules. A job like `test-smoke-gating` can pick up labels from more than one rule, and duplicates are removed automatically.
- Use simple glob patterns such as `test-*` for straightforward matching.
- If you want JJI to extract a value like `version`, use a named regex capture such as `(?P<version>[\d.]+z?)`.

> **Note:** JJI loads metadata rules once per server start. After you edit the rules file, restart the server before testing with `jji metadata preview`.

## Troubleshooting

- The Dashboard does not show metadata filters: add metadata to at least one job first. The filter bar only appears when JJI has stored metadata values to offer.
- `jji metadata set` or `jji metadata import` fails with a permission error: metadata write operations require admin access.
- Rule changes are not taking effect: confirm `METADATA_RULES_FILE` points to the right file, restart JJI, and run `jji metadata rules` or `jji metadata preview` again.
- A job still shows the old tags: existing metadata is preserved. Update it manually, re-import it, or run `jji metadata delete "my-job"` and analyze the job again.
- A multi-label filter is too narrow: selecting more than one label uses AND logic, so a job must contain every selected label.

## Related Pages

- [Manage Users, Access, and Token Usage](manage-users-access-and-token-usage.html)
- [Analyze a Jenkins Job](analyze-a-jenkins-job.html)
- [CLI Command Reference](cli-command-reference.html)
- [REST API Reference](rest-api-reference.html)
- [Configuration and Environment Reference](configuration-and-environment-reference.html)

---

Source: manage-users-access-and-token-usage.md

# Manage Users, Access, and Token Usage

You want to run JJI as a shared service without passing one long-lived admin secret around the team. The safest pattern is to use `ADMIN_KEY` only to bootstrap named admins, optionally restrict write access with `ALLOWED_USERS`, and review AI token usage from the admin tools.

## Prerequisites
- Access to the JJI server configuration so you can set environment variables and restart or redeploy the service.
- A running JJI instance you can reach in a browser or with `jji`.
- `jji` installed if you want to use the CLI examples.
- Either the server `ADMIN_KEY`, or an existing named admin key.

## Quick Example

```dotenv
ADMIN_KEY=change-this-admin-secret
ALLOWED_USERS=alice,bob,carol
SECURE_COOKIES=true
```

```bash
export JJI_SERVER=https://jji.example.com
export JJI_API_KEY="$ADMIN_KEY"

jji admin users create alice
jji admin users list
jji admin token-usage
```

That is enough to bootstrap a shared instance, create the first named admin, and open the admin-only AI token usage tools.

## Step-by-Step

1. Set the bootstrap key and, if needed, the writer allow list.

```dotenv
ADMIN_KEY=change-this-admin-secret
ALLOWED_USERS=alice,bob,carol
SECURE_COOKIES=true
```

Restart or redeploy JJI after changing server environment variables. Keep `ALLOWED_USERS` empty when you want open write access.

| `ALLOWED_USERS` value | Result |
| --- | --- |
| empty | Anyone with a username can submit or modify data |
| `alice,bob,carol` | Only those users can submit or modify data |
| any value | Admin users still bypass the allow list |

> **Note:** The allow list is case-insensitive and affects write actions only. Read-only pages and queries still work.

2. Use the bootstrap admin once to create a named admin.

```bash
jji --server https://jji.example.com --api-key "$ADMIN_KEY" admin users create alice
```

JJI prints the new admin key once. Save it immediately and hand it to the person who will own that account.

> **Warning:** JJI does not let you retrieve an admin key later. If it is lost, rotate it and distribute the replacement.

If you prefer the browser, sign in at `/register` as `admin` with the same `ADMIN_KEY`, then open `Users` and choose `Create Admin`.

3. Switch day-to-day admin work to the named admin key.

```bash
export JJI_SERVER=https://jji.example.com
export JJI_API_KEY="paste-alice-key-here"

jji auth whoami
jji admin users list
```

Use named admin keys for normal operations instead of sharing `ADMIN_KEY`. In the browser, the same key works at `/register` with username `alice`.

> **Note:** `jji auth login` validates credentials, but later CLI commands only keep admin access when you pass `--api-key`, set `JJI_API_KEY`, or save `api_key` in CLI config.

Once the browser recognizes an admin, `Users` and `Token Usage` appear in the navigation.

4. Promote, demote, rotate, or remove admin access.

```bash
jji admin users list
jji admin users change-role bob admin
jji admin users rotate-key alice
jji admin users change-role carol user
jji admin users delete oldadmin --force
```

`jji admin users list` shows named admins and regular users JJI has already seen. Promote a regular user when they need admin access. Rotate a key when you suspect exposure or during a handoff.

| Task | Result |
| --- | --- |
| Promote to `admin` | JJI generates a new admin key |
| Demote to `user` | The admin key is revoked and active admin sessions end |
| Rotate an admin key | The old key stops working immediately |
| Delete a named admin | The account is removed and active admin sessions end |

> **Tip:** There is no separate “create regular user” command. If the person you want to promote is not listed yet, have them open JJI once and save a username.

5. Monitor AI token usage from the browser or the CLI.

```bash
jji admin token-usage
jji admin token-usage --group-by provider
jji admin token-usage --job-id abc-123
```

`Token Usage` is admin-only. The browser view shows Today, Last 7 Days, and Last 30 Days, plus top models, top jobs, and a breakdown table you can group by model, provider, call type, day, week, month, or job.

The default CLI summary reports the same rolling windows. Use `--job-id` when you want the call-by-call breakdown for one analysis.

## Advanced Usage

### Save admin auth in CLI config

```toml
[default]
server = "prod"

[servers.prod]
url = "https://jji.example.com"
username = "alice"
api_key = "paste-admin-key-here"
```

After that, commands like `jji admin users list` and `jji admin token-usage --group-by model` use the saved profile automatically.

> **Warning:** Treat `~/.config/jji/config.toml` as sensitive. It can contain live admin credentials.

### Rotate the bootstrap `ADMIN_KEY`

```dotenv
ADMIN_KEY=replace-this-bootstrap-secret
```

Create and verify at least one named admin first, then change `ADMIN_KEY` on the server and restart or redeploy JJI. Rotating `ADMIN_KEY` only changes the reserved `admin` login; existing named admin keys keep working.

### Filter or export AI token usage

```bash
jji admin token-usage --period month --group-by model
jji admin token-usage --provider claude --group-by job
jji admin token-usage --group-by provider --format csv
```

Use `--period` for quick rolling windows, `--group-by` for spend breakdowns, and `--format csv` when you want to export the grouped data.

See [CLI Command Reference](cli-command-reference.html) for the full `jji admin ...` option list. See [Configuration and Environment Reference](configuration-and-environment-reference.html) for all server-side settings. See [REST API Reference](rest-api-reference.html) if you want to automate the same tasks.

## Troubleshooting

- **`Invalid username or API key`**: For the bootstrap login, the username must be exactly `admin`. For named admins, the username and key must belong to the same account.
- **`Admin access required`**: Your browser session is not admin, or the CLI request is missing `--api-key` or `JJI_API_KEY`.
- **`User not allowed`**: The username is not in `ALLOWED_USERS`. Add it to the allow list, or use an admin key.
- **The user you want to promote is missing**: Have them open JJI once and save a username, then refresh `Users`.
- **A demote or delete action is blocked**: JJI does not let you change or delete your own active admin account, and it will not let you remove the last admin.
- **Username creation fails**: Managed usernames must be 2-50 characters, start with a letter or digit, and may include `.`, `_`, and `-`. The name `admin` is reserved.
- **Browser admin login does not stick on local HTTP**: `SECURE_COOKIES` may still be `true`. Use HTTPS, or set it to `false` only for local HTTP development.

## Related Pages

- [Configure Your Profile and Notifications](configure-your-profile-and-notifications.html)
- [Organize Jobs with Metadata](organize-jobs-with-metadata.html)
- [CLI Command Reference](cli-command-reference.html)
- [REST API Reference](rest-api-reference.html)
- [Configuration and Environment Reference](configuration-and-environment-reference.html)

---

Source: copy-common-analysis-recipes.md

# Copy Common Analysis Recipes

These short recipes cover the most common ways to queue and tune analysis runs from `jji` or `curl`.

> **Tip:** For the full flag list, request fields, and reusable defaults, see [CLI Command Reference](cli-command-reference.html), [REST API Reference](rest-api-reference.html), and [Configuration and Environment Reference](configuration-and-environment-reference.html).


> **Note:** `GET /results/{job_id}` returns `202` while a job is still `pending`, `waiting`, or `running`.

## Queue a Jenkins analysis from the CLI

Use this when you want to kick off analysis now and poll for the final result from a shell script.

```bash
server=https://jji.example.com

job_id="$(
  jji --server "$server" --user alex --json analyze \
    --job-name qe/nightly/api-tests \
    --build-number 1842 \
    --provider claude \
    --model opus-4 \
    --jenkins-url https://jenkins.ci.example.com \
    --jenkins-user svc-jji \
    --jenkins-password jenkins-api-token-123 \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["job_id"])'
)"

while :; do
  result="$(jji --server "$server" --user alex --json status "$job_id")"
  status="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])' <<<"$result")"
  [ "$status" = "completed" ] || [ "$status" = "failed" ] && break
  sleep 15
done

printf '%s\n' "$result"
```

This submits `jji analyze`, captures the returned `job_id`, and polls until the job reaches a terminal state. Use it for terminal automation, cron jobs, or wrapper scripts that need the final JSON document instead of the initial queued response.

- Use `jji --server "$server" --user alex status "$job_id"` if you only want a quick human-readable status check.

## Queue a Jenkins analysis from the REST API

Use this when you want the same background flow from `curl` or another HTTP client.

```bash
server=https://jji.example.com

response="$(
  curl -sS \
    --cookie "jji_username=alex" \
    --header 'Content-Type: application/json' \
    --data '{"job_name":"qe/nightly/api-tests","build_number":1842,"ai_provider":"claude","ai_model":"opus-4","jenkins_url":"https://jenkins.ci.example.com","jenkins_user":"svc-jji","jenkins_password":"jenkins-api-token-123"}' \
    "$server/analyze"
)"

job_id="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["job_id"])' <<<"$response")"

while :; do
  result="$(curl -sS --cookie "jji_username=alex" "$server/results/$job_id")"
  status="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])' <<<"$result")"
  [ "$status" = "completed" ] || [ "$status" = "failed" ] && break
  sleep 15
done

printf '%s\n' "$result"
```

This posts to `/analyze`, saves the queued `job_id`, and polls `/results/{job_id}` until the run finishes. Use it when you want a copy-pasteable API-only flow without relying on a saved CLI profile.

## Wait for Jenkins to finish before analysis from the CLI

Use this when the Jenkins build is still running and you want JJI to wait before it starts analysis.

```bash
jji --server https://jji.example.com --user alex analyze \
  --job-name qe/nightly/api-tests \
  --build-number 1842 \
  --provider claude \
  --model opus-4 \
  --jenkins-url https://jenkins.ci.example.com \
  --jenkins-user svc-jji \
  --jenkins-password jenkins-api-token-123 \
  --wait \
  --poll-interval 2 \
  --max-wait 90
```

This queues the job immediately, then keeps it in the server-side `waiting` state until Jenkins reports that the build is done. Use it when your analysis request may arrive before test execution has completed.

- Set `--max-wait 0` to wait indefinitely.
- `--poll-interval` is in minutes.

## Wait for Jenkins to finish before analysis from the REST API

Use this when you need the same Jenkins-wait behavior from an HTTP request.

```bash
curl -sS \
  --cookie "jji_username=alex" \
  --header 'Content-Type: application/json' \
  --data '{"job_name":"qe/nightly/api-tests","build_number":1842,"ai_provider":"claude","ai_model":"opus-4","jenkins_url":"https://jenkins.ci.example.com","jenkins_user":"svc-jji","jenkins_password":"jenkins-api-token-123","wait_for_completion":true,"poll_interval_minutes":2,"max_wait_minutes":90}' \
  https://jji.example.com/analyze
```

This sends the wait settings directly in the `/analyze` body, so the queued job pauses in JJI until Jenkins finishes or the timeout is reached. It is useful for chatops bots, webhooks, or scripts that submit analysis as soon as a build is triggered.

- Set `"max_wait_minutes": 0` to wait with no deadline.

## Add peer review from the CLI

Use this when you want the primary model to debate the result with one or more peer models.

```bash
jji --server https://jji.example.com --user alex analyze \
  --job-name qe/nightly/api-tests \
  --build-number 1842 \
  --provider claude \
  --model opus-4 \
  --jenkins-url https://jenkins.ci.example.com \
  --jenkins-user svc-jji \
  --jenkins-password jenkins-api-token-123 \
  --peers "cursor:gpt-5.4-xhigh,gemini:gemini-2.5-pro" \
  --peer-analysis-max-rounds 5
```

This runs the main analysis with `claude:opus-4` and sends the result through peer-review rounds with the listed peer models. Use it for noisy failures, release blockers, or any run where you want more than one model to challenge the first answer.

- `--peer-analysis-max-rounds` accepts values from `1` to `10`.

## Add peer review from the REST API

Use this when you want peer analysis from `curl` or an API client.

```bash
curl -sS \
  --cookie "jji_username=alex" \
  --header 'Content-Type: application/json' \
  --data '{"job_name":"qe/nightly/api-tests","build_number":1842,"ai_provider":"claude","ai_model":"opus-4","jenkins_url":"https://jenkins.ci.example.com","jenkins_user":"svc-jji","jenkins_password":"jenkins-api-token-123","peer_ai_configs":[{"ai_provider":"cursor","ai_model":"gpt-5.4-xhigh"},{"ai_provider":"gemini","ai_model":"gemini-2.5-pro"}],"peer_analysis_max_rounds":5}' \
  https://jji.example.com/analyze
```

This sends the peer-review models as `peer_ai_configs` and limits the debate to five rounds. Use it when your automation layer works directly against the API and you want consistent multi-model review behavior.

- Send `"peer_ai_configs": []` to disable a server default just for one request.

## Add extra repository context from the CLI

Use this when the failure only makes sense if the AI can inspect related repos alongside the test repo.

```bash
jji --server https://jji.example.com --user alex analyze \
  --job-name qe/nightly/api-tests \
  --build-number 1842 \
  --provider claude \
  --model opus-4 \
  --jenkins-url https://jenkins.ci.example.com \
  --jenkins-user svc-jji \
  --jenkins-password jenkins-api-token-123 \
  --tests-repo-url https://github.com/acme/api-tests.git:release-4.18 \
  --additional-repos "infra:https://github.com/acme/infra.git:release-4.18,product:https://github.com/acme/product.git:main"
```

This clones the main test repo and the named extra repos into one shared workspace before analysis starts. Use it when the failure depends on product code, deployment code, or shared infrastructure that lives outside the test repo.

- Keep extra repo names short and stable, such as `infra` or `product`, because those names become workspace directory names.
- For a private extra repo on the CLI, append `@token`, for example `infra:https://github.com/acme/infra.git:release-4.18@ghp_exampleinfra123`.

## Add extra repository context from the REST API

Use this when you need the same multi-repo workspace through the API.

```bash
curl -sS \
  --cookie "jji_username=alex" \
  --header 'Content-Type: application/json' \
  --data '{"job_name":"qe/nightly/api-tests","build_number":1842,"ai_provider":"claude","ai_model":"opus-4","jenkins_url":"https://jenkins.ci.example.com","jenkins_user":"svc-jji","jenkins_password":"jenkins-api-token-123","tests_repo_url":"https://github.com/acme/api-tests.git:release-4.18","additional_repos":[{"name":"infra","url":"https://github.com/acme/infra.git","ref":"release-4.18"},{"name":"product","url":"https://github.com/acme/product.git","ref":"main"}]}' \
  https://jji.example.com/analyze
```

This sends the extra repos as structured `additional_repos` entries, each with its own name and optional ref. Use it when your analysis caller already has repo metadata and you want to pass it directly instead of encoding it into one CLI string.

- Add `"token":"ghp_exampleinfra123"` to an extra repo object when the clone needs token-based access.
- Send `"additional_repos": []` to disable a server default for one request.

## Analyze against a self-signed Jenkins from the CLI

Use this when JJI can reach Jenkins but certificate validation fails against an internal or self-signed TLS endpoint.

```bash
jji --server https://jji.example.com --user alex analyze \
  --job-name qe/nightly/api-tests \
  --build-number 1842 \
  --provider claude \
  --model opus-4 \
  --jenkins-url https://jenkins.ci.example.com \
  --jenkins-user svc-jji \
  --jenkins-password jenkins-api-token-123 \
  --no-jenkins-ssl-verify
```

This disables certificate verification only for the Jenkins API calls used by this analysis request. Use it for one-off runs against internal Jenkins instances with self-signed or privately issued certificates.

- If the JJI server itself has a self-signed cert, use the CLI's global `--insecure` or `--no-verify-ssl` option instead.

## Analyze against a self-signed Jenkins from the REST API

Use this when you need the same Jenkins TLS override in an API request.

```bash
curl -sS \
  --cookie "jji_username=alex" \
  --header 'Content-Type: application/json' \
  --data '{"job_name":"qe/nightly/api-tests","build_number":1842,"ai_provider":"claude","ai_model":"opus-4","jenkins_url":"https://jenkins.ci.example.com","jenkins_user":"svc-jji","jenkins_password":"jenkins-api-token-123","jenkins_ssl_verify":false}' \
  https://jji.example.com/analyze
```

This passes `jenkins_ssl_verify: false` in the request body so the server skips Jenkins certificate verification for this run. It is the API equivalent of `--no-jenkins-ssl-verify`.

## Force analysis for a build that passed from the CLI

Use this when Jenkins says the build succeeded but you still want JJI to inspect it.

```bash
jji --server https://jji.example.com --user alex analyze \
  --job-name qe/nightly/api-tests \
  --build-number 1842 \
  --provider claude \
  --model opus-4 \
  --jenkins-url https://jenkins.ci.example.com \
  --jenkins-user svc-jji \
  --jenkins-password jenkins-api-token-123 \
  --force
```

By default, JJI returns early on `SUCCESS` builds with a short "Build passed successfully" summary and no failure analysis. `--force` bypasses that early return so you can inspect suspicious passes, flaky-success cases, or unexpected console output.

- If you want this behavior as a reusable default, move it into server configuration or environment defaults described in [Configuration and Environment Reference](configuration-and-environment-reference.html).

## Force analysis for a build that passed from the REST API

Use this when you want the same passed-build override from an HTTP request.

```bash
curl -sS \
  --cookie "jji_username=alex" \
  --header 'Content-Type: application/json' \
  --data '{"job_name":"qe/nightly/api-tests","build_number":1842,"ai_provider":"claude","ai_model":"opus-4","jenkins_url":"https://jenkins.ci.example.com","jenkins_user":"svc-jji","jenkins_password":"jenkins-api-token-123","force":true}' \
  https://jji.example.com/analyze
```

This sets `force: true` on the `/analyze` request so the server keeps going even when Jenkins reports `SUCCESS`. Use it when a passing build still needs AI inspection for debugging or audit reasons.

## Related Pages

- [Analyze a Jenkins Job](analyze-a-jenkins-job.html)
- [Customize AI Analysis](customize-ai-analysis.html)
- [CLI Command Reference](cli-command-reference.html)
- [REST API Reference](rest-api-reference.html)
- [Configuration and Environment Reference](configuration-and-environment-reference.html)

---

Source: copy-common-deployment-recipes.md

# Copy Common Deployment Recipes

> **Note:** These recipes assume you are running from a checkout of the repository. For the full server env list and endpoint shapes, see [Configuration and Environment Reference](configuration-and-environment-reference.html) and [REST API Reference](rest-api-reference.html).

## Start a Simple Docker Compose Instance

Run the repository's shipped Compose stack with a persistent SQLite database and the combined UI/API on port `8000`.

```bash
cat > .env <<'EOF'
JENKINS_URL=https://ci.example.com
JENKINS_USER=svc-jji
JENKINS_PASSWORD=jenkins-api-token-123
AI_PROVIDER=claude
AI_MODEL=claude-opus-4-1
ANTHROPIC_API_KEY=anthropic-demo-key-123
JJI_ENCRYPTION_KEY=change-this-before-prod-32chars
PUBLIC_BASE_URL=http://localhost:8000
SECURE_COOKIES=false
LOG_LEVEL=INFO
EOF

mkdir -p data
docker compose up -d --build
docker compose ps
curl -fsS http://localhost:8000/health
```

This uses the repository's `docker-compose.yaml` as-is: it builds `Dockerfile`, mounts `./data` to `/data`, and keeps the container healthy with a `/health` probe. It is the fastest way to get a persistent JJI instance running from the repo checkout. After it is up, see [Analyze Your First Jenkins Job](analyze-your-first-jenkins-job.html) for the first-run flow.

- For public HTTPS, change `PUBLIC_BASE_URL` to your external `https://...` URL and set `SECURE_COOKIES=true`.
- If Jenkins uses a self-signed certificate, add `JENKINS_SSL_VERIFY=false` to `.env`.
- If you change `PORT`, update the Compose port mapping and healthcheck to match.

## Run Container Dev Mode with Hot Reload

Start backend reload and frontend HMR inside the container without switching away from Docker.

```bash
cat > .env <<'EOF'
JENKINS_URL=https://ci.example.com
JENKINS_USER=svc-jji
JENKINS_PASSWORD=jenkins-api-token-123
AI_PROVIDER=claude
AI_MODEL=claude-opus-4-1
ANTHROPIC_API_KEY=anthropic-demo-key-123
JJI_ENCRYPTION_KEY=dev-only-encryption-key
SECURE_COOKIES=false
LOG_LEVEL=DEBUG
EOF

cat > docker-compose.override.yaml <<'EOF'
services:
  jenkins-job-insight:
    environment:
      - DEV_MODE=true
    ports:
      - "5173:5173"
    volumes:
      - ./src:/app/src
      - ./frontend:/app/frontend
EOF

mkdir -p data
docker compose up --build
```

With `DEV_MODE=true`, `entrypoint.sh` starts Vite on `5173` and appends `uvicorn --reload --reload-dir /app/src` for backend code changes. Open `http://localhost:5173` for the UI; Vite proxies API traffic to the backend on `http://localhost:8000`.

> **Warning:** Keep `SECURE_COOKIES=false` only for local HTTP development.

- The first startup may run `npm install` inside the container if `frontend/node_modules` is missing.

## Put JJI Behind a Reverse Proxy

Terminate traffic at a proxy and keep the JJI app itself off the public port.

```bash
cat > .env.proxy <<'EOF'
JENKINS_URL=https://ci.example.com
JENKINS_USER=svc-jji
JENKINS_PASSWORD=jenkins-api-token-123
AI_PROVIDER=claude
AI_MODEL=claude-opus-4-1
ANTHROPIC_API_KEY=anthropic-demo-key-123
JJI_ENCRYPTION_KEY=change-this-before-prod-32chars
PUBLIC_BASE_URL=http://localhost:8080
SECURE_COOKIES=false
EOF

cat > nginx.conf <<'EOF'
events {}
http {
  server {
    listen 80;
    server_name _;
    location / {
      proxy_pass http://jji:8000;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
    }
  }
}
EOF

cat > compose.proxy.yaml <<'EOF'
services:
  jji:
    build: .
    env_file:
      - .env.proxy
    volumes:
      - ./data:/data
  nginx:
    image: nginx:alpine
    depends_on:
      - jji
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
EOF

mkdir -p data
docker compose -f compose.proxy.yaml up -d --build
```

This makes the proxy the only entry point and keeps JJI itself on the internal Compose network. `PUBLIC_BASE_URL` matters because JJI does not derive external links from request headers, so set it to the exact URL users will open.

- For public HTTPS, terminate TLS at the proxy, change `PUBLIC_BASE_URL` to `https://...`, and set `SECURE_COOKIES=true`.
- If your auth layer injects `X-Forwarded-User`, add `TRUST_PROXY_HEADERS=true` to JJI and `proxy_set_header X-Forwarded-User $http_x_forwarded_user;` to the proxy.
- JJI ignores `X-Forwarded-User: admin`; `admin` stays reserved for real admin auth.

## Smoke-Test the Health Endpoints

Check that the service is up and inspect the detailed dependency view before you route traffic to it.

```bash
set -euo pipefail

BASE_URL=http://localhost:8000

echo "Lightweight health:"
curl -fsS "$BASE_URL/health"
echo
echo
echo "Detailed health:"
curl -fsS "$BASE_URL/api/health"
echo
```

`/health` is the lightweight endpoint used by the container healthcheck. `/api/health` adds database, Jenkins, AI provider, and Report Portal checks, and returns `503` only when JJI is actually `unhealthy`.

- `/health`, `/api/health`, and `/metrics` are excluded from JJI's rolling error counters, so normal probes do not skew `jji_error_rate`.

## Scrape Built-In Prometheus Metrics

Collect JJI's rolling-window metrics without adding an exporter sidecar.

```bash
cat > prometheus.yml <<'EOF'
global:
  scrape_interval: 30s

scrape_configs:
  - job_name: jenkins-job-insight
    metrics_path: /metrics
    static_configs:
      - targets:
          - jenkins-job-insight:8000
EOF

cat > compose.metrics.yaml <<'EOF'
services:
  prometheus:
    image: prom/prometheus
    command:
      - --config.file=/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
EOF

docker compose -f docker-compose.yaml -f compose.metrics.yaml up -d --build
```

This starts Prometheus alongside the repository's Compose service and scrapes JJI directly on the Compose network. JJI publishes rolling-window gauges for `jji_requests_total`, `jji_errors_total`, `jji_error_rate`, `jji_errors_by_class`, plus `jji_health_up` and `jji_active_analyses`.

- Open `http://localhost:9090/targets` to confirm the scrape is `UP`.
- If Prometheus runs elsewhere, replace `jenkins-job-insight:8000` with your service DNS name or external host:port.
- `jji_health_up` is `1` for both `healthy` and `degraded`, and `0` only when `/api/health` is `unhealthy`.

## Send Lightweight Slack and Email Alerts

Let a single JJI instance notify you when 5xx responses spike, without waiting on external monitoring.

```bash
cat > .env <<'EOF'
JENKINS_URL=https://ci.example.com
JENKINS_USER=svc-jji
JENKINS_PASSWORD=jenkins-api-token-123
AI_PROVIDER=claude
AI_MODEL=claude-opus-4-1
ANTHROPIC_API_KEY=anthropic-demo-key-123
JJI_ENCRYPTION_KEY=change-this-before-prod-32chars
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/EXAMPLE/WEBHOOK/URL
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=jji-alerts@example.com
SMTP_PASSWORD=smtp-app-password-123
SMTP_FROM=jji-alerts@example.com
ALERT_EMAIL_TO=sre@example.com
EOF

mkdir -p data
docker compose up -d --build
```

JJI dispatches best-effort Slack and email alerts when its 5-minute rolling 5xx error rate goes above 50% and at least 10 requests have been seen. Duplicate alert events are throttled for 5 minutes, so this works well as a lightweight single-instance safety net.

- Slack webhook URLs should use `https://`.
- If you set `SMTP_HOST` without `ALERT_EMAIL_TO`, JJI starts but warns that email alerts will not be sent.

## Apply the OpenShift PrometheusRule

Use the repository's current OpenShift alert thresholds for warning, critical, and health-down conditions.

```bash
oc apply -f - <<'EOF'
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: jenkins-job-insight-alerts
  labels:
    app: jenkins-job-insight
spec:
  groups:
    - name: jenkins-job-insight.rules
      rules:
        - alert: JJIHighErrorRate
          expr: jji_error_rate > 0.1
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "JJI high error rate"
            description: >
              JJI error rate is {{ $value | humanizePercentage }} over the
              rolling window (threshold 10%).
        - alert: JJIVeryHighErrorRate
          expr: jji_error_rate > 0.5
          for: 2m
          labels:
            severity: critical
          annotations:
            summary: "JJI critical error rate"
            description: >
              JJI error rate is {{ $value | humanizePercentage }} over the
              rolling window (threshold 50%).
        - alert: JJIHealthUnhealthy
          expr: jji_health_up == 0
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "JJI health check failing"
            description: "JJI /api/health is returning 503 (unhealthy) for over 5 minutes."
EOF
```

This matches the rule shipped in `docs/openshift-prometheus-rule.yaml`. Use it after OpenShift monitoring is scraping JJI's `/metrics` endpoint so you get two error-rate thresholds and one hard health-down alert.

- Add any labels your Alertmanager routing expects before applying the rule.
- The container image is already prepared for OpenShift-style arbitrary UID runtimes; the main deployment requirement is a writable `/data` volume.

## Related Pages

- [Analyze Your First Jenkins Job](analyze-your-first-jenkins-job.html)
- [Analyze a Jenkins Job](analyze-a-jenkins-job.html)
- [Manage Users, Access, and Token Usage](manage-users-access-and-token-usage.html)
- [REST API Reference](rest-api-reference.html)
- [Configuration and Environment Reference](configuration-and-environment-reference.html)

---

Source: cli-command-reference.md

# CLI Command Reference

All commands below also accept the global options in this page. For request and response bodies, see [REST API Reference](rest-api-reference.html).

## Command groups
| Group | Commands |
| --- | --- |
| Top level | `health`, `analyze`, `re-analyze`, `status`, `classify`, `capabilities`, `jira-projects`, `jira-security-levels`, `ai-configs`, `mentionable-users`, `mentions`, `mentions-mark-read`, `mentions-mark-all-read`, `preview-issue`, `create-issue`, `validate-token`, `push-reportportal`, `override-classification` |
| `results` | `list`, `dashboard`, `show`, `delete`, `review-status`, `set-reviewed`, `enrich-comments` |
| `history` | `test`, `search`, `stats`, `failures` |
| `comments` | `list`, `add`, `delete` |
| `classifications` | `list` |
| `metadata` | `list`, `get`, `set`, `delete`, `import`, `rules`, `preview` |
| `auth` | `login`, `logout`, `whoami` |
| `admin users` | `list`, `create`, `delete`, `rotate-key`, `change-role` |
| `admin` | `token-usage` |
| `config` | `show`, `servers`, `completion` |

## Global options
### `jji [GLOBAL OPTIONS] COMMAND`
Applies connection, authentication, SSL, and JSON behavior to the invoked command.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--server`, `-s` | string | profile default or required | Server profile name or full server URL. Env: `JJI_SERVER`. |
| `--json` | boolean | `false` | Pretty-print JSON instead of text output. |
| `--user` | string | profile `username` or empty | Username used for comments and review actions. Env: `JJI_USERNAME`. |
| `--api-key` | string | profile `api_key` or empty | Bearer token for admin-authenticated commands. Env: `JJI_API_KEY`. |
| `--no-verify-ssl` | boolean | profile `no_verify_ssl` or `false` | Disable TLS certificate verification. Env: `JJI_NO_VERIFY_SSL`. |
| `--verify-ssl` | boolean | unset | Force TLS verification on, overriding profile SSL settings. |
| `--insecure` | boolean | `false` | Alias for `--no-verify-ssl`. |

```bash
jji --server prod --api-key "$JJI_API_KEY" results dashboard
```

**Return value/effect:** Applies the selected server, auth, SSL, and output mode to the command that follows.

> **Note:** `--json` works both before the command (`jji --json health`) and after the leaf command (`jji health --json`).
>


> **Note:** If `--server` is a full `http://` or `https://` URL, profile defaults are not loaded for that invocation.
>


> **Warning:** `--verify-ssl` and `--insecure` cannot be used together.

## Output modes
### `--json`
Prints the full JSON payload for the selected command.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--json` | boolean | `false` | Switch from table/plain-text output to pretty-printed JSON. |

```bash
jji results show job-123 --json
```

**Return value/effect:** Returns the raw command payload as formatted JSON.

### `jji admin token-usage --format csv`
Enables CSV output for token usage reporting.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--format` | string | `table` | Output format for `admin token-usage`: `table`, `json`, or `csv`. |

```bash
jji admin token-usage --group-by provider --format csv
```

**Return value/effect:** Prints CSV headers and rows for token-usage data.

> **Note:** `--format csv` is only supported by `jji admin token-usage`.
>


> **Note:** Global `--json` overrides `--format`.

## Profile-driven usage
### Config file layout
Profiles are loaded from `config.toml`.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `CONFIG_FILE` | path | `$XDG_CONFIG_HOME/jji/config.toml` or `~/.config/jji/config.toml` | CLI profile file location. |
| `[default].server` | string | unset | Default profile name used when `--server` is omitted. |
| `[defaults]` | table | unset | Values merged into every named profile. |
| `[servers.<name>]` | table | required per profile | Named server profile. |
| `XDG_CONFIG_HOME` | environment variable | unset | Overrides the base config directory. |

```toml
[default]
server = "dev"

[defaults]
jenkins_url = "https://jenkins.example.com"
ai_provider = "claude"
ai_model = "opus-4"

[servers.dev]
url = "http://localhost:8000"
username = "alice"

[servers.prod]
url = "https://jji.example.com"
username = "alice"
api_key = "prod-api-key"
```

**Return value/effect:** Commands can use a profile name with `--server`, or use the default profile automatically.

> **Note:** CLI flags and env vars override profile values.
>


> **Tip:** See [Configuration and Environment Reference](configuration-and-environment-reference.html) for the full configuration surface.

### Profile keys: connection and auth
These keys are read directly by the CLI.

| Key | Type | Default | Used by | Description |
| --- | --- | --- | --- | --- |
| `url` | string | required | all commands | Base URL for the server profile. |
| `username` | string | `""` | global `--user` fallback | Default CLI username. |
| `no_verify_ssl` | boolean | `false` | global SSL handling | Default TLS verification behavior. |
| `api_key` | string | `""` | global `--api-key` fallback | Default admin bearer token. |

```bash
jji --server prod health
```

**Return value/effect:** Resolves the server connection without repeating the full URL or admin key on every command.

### Profile keys: analysis defaults
These keys are merged into `jji analyze` when the matching CLI option is omitted.

| Key | Type | Default | Used by | Description |
| --- | --- | --- | --- | --- |
| `jenkins_url` | string | `""` | `analyze` | Jenkins base URL. |
| `jenkins_user` | string | `""` | `analyze` | Jenkins username. |
| `jenkins_password` | string | `""` | `analyze` | Jenkins password or token. |
| `jenkins_ssl_verify` | boolean/null | unset | `analyze` | Jenkins TLS verification override. |
| `jenkins_timeout` | integer | `0` | `analyze` | Jenkins request timeout override. `0` means no CLI override. |
| `tests_repo_url` | string | `""` | `analyze` | Tests repository URL. |
| `ai_provider` | string | `""` | `analyze` | Default AI provider. |
| `ai_model` | string | `""` | `analyze` | Default AI model. |
| `ai_cli_timeout` | integer | `0` | `analyze` | AI CLI timeout override in minutes. `0` means no CLI override. |
| `enable_jira` | boolean/null | unset | `analyze` | Default Jira enable/disable state. |
| `peers` | string | `""` | `analyze` | Peer AI list in `provider:model,provider:model` format. |
| `peer_analysis_max_rounds` | integer | `0` | `analyze` | Peer analysis round override. `0` means no CLI override. |
| `additional_repos` | string | `""` | `analyze` | Extra repo list in `name:url`, optional `:ref`, optional trailing `@token` format. |
| `wait_for_completion` | boolean/null | unset | `analyze` | Default wait behavior before analysis starts. |
| `poll_interval_minutes` | integer | `0` | `analyze` | Poll interval override. `0` means no CLI override. |
| `max_wait_minutes` | integer | `0` | `analyze` | Max wait override. `0` means no CLI override. |
| `force` | boolean/null | unset | `analyze` | Default force-analysis behavior. |

```bash
jji analyze --job-name periodic-e2e --build-number 274
```

**Return value/effect:** Sends profile defaults together with the required job name and build number.

### Profile keys: Jira and GitHub defaults
These keys are used by issue-preview, issue-creation, and Jira lookup commands.

| Key | Type | Default | Used by | Description |
| --- | --- | --- | --- | --- |
| `jira_url` | string | `""` | `analyze` | Jira base URL for analysis-time matching. |
| `jira_email` | string | `""` | `analyze`, `jira-projects`, `jira-security-levels`, Jira issue commands | Jira email value used when required. |
| `jira_api_token` | string | `""` | `analyze` | Jira Cloud API token for analysis-time matching. |
| `jira_pat` | string | `""` | `analyze` | Jira Server/DC PAT for analysis-time matching. |
| `jira_token` | string | `""` | `jira-projects`, `jira-security-levels`, Jira issue commands | Jira token fallback for CLI lookup and issue commands. |
| `jira_project_key` | string | `""` | `analyze`, Jira issue commands | Default Jira project key. |
| `jira_security_level` | string | `""` | Jira issue commands | Default Jira security level name. |
| `jira_ssl_verify` | boolean/null | unset | `analyze` | Jira TLS verification override. |
| `jira_max_results` | integer | `0` | `analyze` | Jira search limit override. `0` means no CLI override. |
| `github_token` | string | `""` | `analyze`, GitHub issue commands | GitHub token fallback. |
| `github_repo_url` | string | `""` | GitHub issue commands | Default GitHub repository URL override. |

```bash
jji preview-issue job-123 --test tests.e2e.test_login --type jira
```

**Return value/effect:** Uses profile tracker defaults when matching CLI options are not supplied.

## Server and discovery
Global options are omitted from the tables below unless they change command behavior.

### `jji health`
Checks server health.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only global options. |

```bash
jji health
```

**Return value/effect:** Default output prints `Status`, optional component `Checks`, and optional error-rate information. JSON mode returns the full health payload.

### `jji capabilities`
Shows server support for post-analysis automation.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only global options. |

```bash
jji capabilities
```

**Return value/effect:** Prints the capability object, including GitHub/Jira automation flags.

### `jji ai-configs`
Lists provider/model pairs from completed analyses.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only global options. |

```bash
jji ai-configs
```

**Return value/effect:** Default output prints a two-column table of provider/model pairs. If no completed analyses have recorded AI settings, the command prints `No AI configurations found from completed analyses.`

### `jji jira-projects`
Lists Jira projects available to the supplied Jira credentials.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--query` | string | `""` | Filter the project list by a search string. |
| `--jira-token` | string | profile `jira_token` or empty | Jira token override. |
| `--jira-email` | string | profile `jira_email` or empty | Jira email override. |

```bash
jji jira-projects --query platform
```

**Return value/effect:** Default output prints project `KEY` and `NAME`. JSON mode returns the full project array.

### `jji jira-security-levels PROJECT_KEY`
Lists Jira issue security levels for a project.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `PROJECT_KEY` | string | — | Jira project key to inspect. |
| `--jira-token` | string | profile `jira_token` or empty | Jira token override. |
| `--jira-email` | string | profile `jira_email` or empty | Jira email override. |

```bash
jji jira-security-levels PROJ
```

**Return value/effect:** Default output prints security level names and descriptions. If none are returned, the command prints `No security levels found.`

## Analysis jobs
### `jji analyze`
Queues a Jenkins build for analysis.

**Required options**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--job-name`, `-j` | string | — | Jenkins job name. |
| `--build-number`, `-b` | integer | — | Build number to analyze. Must be greater than `0`. |

**AI and context options**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--provider` | string | profile `ai_provider` or omitted | AI provider, for example `claude`, `gemini`, or `cursor`. |
| `--model` | string | profile `ai_model` or omitted | AI model name. |
| `--raw-prompt` | string | omitted | Extra prompt text appended to the analysis request. |
| `--peers` | string | profile `peers` or omitted | Peer AIs in `provider:model,provider:model` format. |
| `--peer-analysis-max-rounds` | integer | profile `peer_analysis_max_rounds` or omitted | Debate rounds for peer analysis. Valid range: `1` to `10`. |
| `--additional-repos` | string | profile `additional_repos` or omitted | Extra repos in `name:url`, optional `:ref`, optional trailing `@token` format. |

**Jenkins, Jira, and GitHub options**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--jira/--no-jira` | boolean | profile `enable_jira` or omitted | Enable or disable Jira matching for this run. |
| `--jenkins-url` | string | profile `jenkins_url` or env `JENKINS_URL` | Jenkins base URL. |
| `--jenkins-user` | string | profile `jenkins_user` or env `JENKINS_USER` | Jenkins username. |
| `--jenkins-password` | string | profile `jenkins_password` or env `JENKINS_PASSWORD` | Jenkins password or token. |
| `--jenkins-ssl-verify/--no-jenkins-ssl-verify` | boolean | profile `jenkins_ssl_verify` or omitted | Jenkins TLS verification override. |
| `--jenkins-timeout` | integer | profile `jenkins_timeout` or omitted | Jenkins API timeout in seconds. Must be greater than `0`. |
| `--jenkins-artifacts-max-size-mb` | integer | omitted | Artifact size cap in MB. Must be greater than `0`. |
| `--get-job-artifacts/--no-get-job-artifacts` | boolean | omitted | Download or skip Jenkins artifacts for AI context. |
| `--tests-repo-url` | string | profile `tests_repo_url` or env `TESTS_REPO_URL` | Tests repository URL. |
| `--jira-url` | string | profile `jira_url` or env `JIRA_URL` | Jira base URL. |
| `--jira-email` | string | profile `jira_email` or env `JIRA_EMAIL` | Jira Cloud email. |
| `--jira-api-token` | string | profile `jira_api_token` or env `JIRA_API_TOKEN` | Jira Cloud API token. |
| `--jira-pat` | string | profile `jira_pat` or env `JIRA_PAT` | Jira Server/DC PAT. |
| `--jira-project-key` | string | profile `jira_project_key` or env `JIRA_PROJECT_KEY` | Jira project key. |
| `--jira-ssl-verify/--no-jira-ssl-verify` | boolean | profile `jira_ssl_verify` or omitted | Jira TLS verification override. |
| `--jira-max-results` | integer | profile `jira_max_results` or omitted | Jira search limit. Must be greater than `0`. |
| `--github-token` | string | profile `github_token` or env `GITHUB_TOKEN` | GitHub token. |
| `--ai-cli-timeout` | integer | profile `ai_cli_timeout` or omitted | AI CLI timeout in minutes. Must be greater than `0`. |

**Monitoring options**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--wait/--no-wait` | boolean | profile `wait_for_completion` or omitted | Wait for Jenkins completion before analysis. |
| `--poll-interval` | integer | profile `poll_interval_minutes` or omitted | Poll interval in minutes. Must be greater than `0`. |
| `--max-wait` | integer | profile `max_wait_minutes` or omitted | Max wait in minutes. Must be `0` or greater. |
| `--force/--no-force` | boolean | profile `force` or omitted | Force analysis even when the build succeeded. |

```bash
jji analyze \
  --job-name periodic-e2e \
  --build-number 274 \
  --provider claude \
  --model opus-4 \
  --wait \
  --poll-interval 2 \
  --additional-repos "infra:https://github.com/org/infra:main"
```

**Return value/effect:** Default output prints the queued `job_id`, queued `status`, and poll URL. JSON mode returns the full queue response.

> **Warning:** `--peer-analysis-max-rounds` must be between `1` and `10`.
>


> **Warning:** Invalid `--peers` and `--additional-repos` strings exit with an error.
>


> **Tip:** See [Copy Common Analysis Recipes](copy-common-analysis-recipes.html) for copy-ready command combinations built from these flags.

### `jji re-analyze JOB_ID`
Queues a new analysis using the settings from a previous analysis.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Existing analysis job ID. |

```bash
jji re-analyze job-123
```

**Return value/effect:** Default output prints the new queued `job_id`, queued `status`, and poll URL. JSON mode returns the full queue response.

### `jji status JOB_ID`
Shows the status of an analysis job.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID to inspect. |

```bash
jji status job-123
```

**Return value/effect:** Default output prints only `job_id` and `status`. JSON mode returns the full stored result payload.

### `jji results list`
Lists recent analysis jobs.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--limit`, `-l` | integer | `50` | Maximum number of jobs to return. |

```bash
jji results list --limit 20
```

**Return value/effect:** Default output prints a table with job ID, status, Jenkins URL, and creation time. JSON mode returns the full array.

### `jji results dashboard`
Lists analysis jobs with dashboard summary fields.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only global options. |

```bash
jji results dashboard
```

**Return value/effect:** Default output prints job name, build number, status, failure count, reviewed count, comment count, and creation time. JSON mode returns the full array.

### `jji results show JOB_ID`
Shows the stored result for a job.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID to display. |
| `--full`, `-f` | boolean | `false` | Print the full JSON result without requiring global `--json`. |

```bash
jji results show job-123 --full
```

**Return value/effect:** Default output prints a short summary. `--full` and `--json` print the complete stored result.

### `jji results delete [JOB_ID ...]`
Deletes one or more stored jobs.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID ...` | string list | empty | One or more job IDs to delete. |
| `--all` | boolean | `false` | Delete all jobs returned by the dashboard. |
| `--confirm` | boolean | `false` | Required together with `--all`. |

```bash
jji results delete job-123 job-124
```

**Return value/effect:** One job prints `Deleted job ...`. Multiple jobs print a deleted count and any failed IDs. JSON mode returns the raw delete response.

> **Warning:** `--all` cannot be combined with explicit job IDs.
>


> **Warning:** `--all` requires `--confirm`.

### `jji results review-status JOB_ID`
Shows review counters for a stored analysis.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID. |

```bash
jji results review-status job-123
```

**Return value/effect:** Default output prints `total_failures`, `reviewed_count`, and `comment_count`. JSON mode returns the full response.

### `jji results set-reviewed JOB_ID`
Sets or clears the reviewed state for one failure.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID. |
| `--test`, `-t` | string | — | Failure test name. |
| `--reviewed/--not-reviewed` | boolean | — | Mark the failure reviewed or not reviewed. |
| `--child-job` | string | `""` | Child job name for pipeline child failures. |
| `--child-build` | integer | `0` | Child build number. |

```bash
jji results set-reviewed job-123 --test tests.e2e.test_login --reviewed
```

**Return value/effect:** Default output prints `Marked as reviewed` or `Marked as not reviewed`, including the reviewer when returned. JSON mode returns the raw response.

> **Warning:** `--child-build` must be `0` or greater.
>


> **Warning:** A positive `--child-build` requires `--child-job`.

### `jji results enrich-comments JOB_ID`
Refreshes comment-linked ticket and PR status data.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID. |

```bash
jji results enrich-comments job-123
```

**Return value/effect:** Default output prints `Enriched N comment(s).` JSON mode returns the raw enrichment response.

## Failure history and classification
### `jji history test TEST_NAME`
Shows history for one test.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `TEST_NAME` | string | — | Fully qualified test name. |
| `--limit`, `-l` | integer | `20` | Maximum recent runs to include. |
| `--job-name`, `-j` | string | `""` | Restrict history to one Jenkins job name. |
| `--exclude-job-id` | string | `""` | Exclude one analysis job ID from the lookup. |

```bash
jji history test tests.e2e.test_login --limit 10
```

**Return value/effect:** Default output prints top-level history fields and optional `Recent runs` and `Comments` tables. JSON mode returns the full history object.

### `jji history search --signature SIGNATURE`
Finds failures that share one error signature.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--signature`, `-s` | string | — | Error signature hash to search for. |
| `--exclude-job-id` | string | `""` | Exclude one analysis job ID from the lookup. |

```bash
jji history search --signature 8b9f4d...
```

**Return value/effect:** Default output prints total occurrences, unique test count, and a table of matching tests. JSON mode returns the full search object.

### `jji history stats JOB_NAME`
Shows aggregate failure statistics for one Jenkins job.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_NAME` | string | — | Jenkins job name. |
| `--exclude-job-id` | string | `""` | Exclude one analysis job ID from the lookup. |

```bash
jji history stats periodic-e2e
```

**Return value/effect:** Default output prints analyzed build counts, failure rate, and an optional `Most common failures` table. JSON mode returns the full stats object.

### `jji history failures`
Lists paginated failure history.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--limit`, `-l` | integer | `50` | Page size. |
| `--offset`, `-o` | integer | `0` | Starting offset. |
| `--search`, `-s` | string | `""` | Test-name substring filter. |
| `--classification`, `-c` | string | `""` | Classification filter. |
| `--job-name`, `-j` | string | `""` | Jenkins job-name filter. |

```bash
jji history failures --classification "PRODUCT BUG" --limit 25
```

**Return value/effect:** Default output prints a total/offset line and a table of matching failures. JSON mode returns the paginated payload.

### `jji classify TEST_NAME`
Creates a manual classification for one failure.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `TEST_NAME` | string | — | Fully qualified test name. |
| `--type`, `-t` | string | — | Classification value: `FLAKY`, `REGRESSION`, `INFRASTRUCTURE`, `KNOWN_BUG`, or `INTERMITTENT`. |
| `--job-id` | string | — | Analysis job ID the classification applies to. |
| `--reason`, `-r` | string | `""` | Free-text reason. |
| `--job-name`, `-j` | string | `""` | Job name override. |
| `--references` | string | `""` | Bug URLs or ticket keys. |
| `--child-job` | string | `""` | Child job name. |
| `--child-build` | integer | `0` | Child build number. |

```bash
jji classify tests.e2e.test_login --type REGRESSION --job-id job-123 --reason "fails after merge"
```

**Return value/effect:** Default output prints the created classification ID. JSON mode returns the full creation response.

### `jji override-classification JOB_ID`
Overrides the analysis classification on one failure.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID. |
| `--test`, `-t` | string | — | Failure test name. |
| `--classification`, `-c` | string | — | Override value: `CODE ISSUE`, `PRODUCT BUG`, or `INFRASTRUCTURE`. |
| `--child-job` | string | `""` | Child job name. |
| `--child-build` | integer | `0` | Child build number. |

```bash
jji override-classification job-123 --test tests.e2e.test_login --classification "PRODUCT BUG"
```

**Return value/effect:** Default output prints the new classification. JSON mode returns the full response.

### `jji classifications list`
Lists stored classifications.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--job-id` | string | `""` | Filter by job ID. |
| `--test-name`, `-t` | string | `""` | Filter by test name. |
| `--type`, `-c` | string | `""` | Filter by classification. |
| `--job-name`, `-j` | string | `""` | Filter by job name. |
| `--parent-job-name` | string | `""` | Filter by parent job name. |

```bash
jji classifications list --job-name periodic-e2e --type REGRESSION
```

**Return value/effect:** Default output prints a table of classification records. If none match, the command prints `No classifications found.`

## Comments and mentions
### `jji comments list JOB_ID`
Lists comments for a stored analysis.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID. |

```bash
jji comments list job-123
```

**Return value/effect:** Default output prints a table of comments. JSON mode returns the full response object, including `comments` and `reviews`.

### `jji comments add JOB_ID`
Adds a comment to one failure.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID. |
| `--test`, `-t` | string | — | Failure test name. |
| `--message`, `-m` | string | — | Comment text. |
| `--child-job` | string | `""` | Child job name. |
| `--child-build` | integer | `0` | Child build number. |

```bash
jji comments add job-123 --test tests.e2e.test_login --message "tracking in PROJ-456"
```

**Return value/effect:** Default output prints the created comment ID. JSON mode returns the full creation response.

### `jji comments delete JOB_ID COMMENT_ID`
Deletes one comment.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID. |
| `COMMENT_ID` | integer | — | Comment ID to delete. |

```bash
jji comments delete job-123 42
```

**Return value/effect:** Default output prints `Comment deleted.` JSON mode returns the raw delete response.

### `jji mentionable-users`
Lists usernames that can be mentioned in comments.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only global options. |

```bash
jji mentionable-users
```

**Return value/effect:** Default output prints one username per line. JSON mode returns `{ "usernames": [...] }`.

### `jji mentions`
Lists the current user's mentions.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--limit`, `-l` | integer | `50` | Maximum mentions to return. |
| `--offset`, `-o` | integer | `0` | Pagination offset. |
| `--unread` | boolean | `false` | Restrict output to unread mentions. |

```bash
jji mentions --unread
```

**Return value/effect:** Default output prints a summary line and a mentions table. JSON mode returns the full paginated mentions payload.

### `jji mentions-mark-read --ids IDS`
Marks selected mentions as read.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--ids` | string | — | Comma-separated positive integer comment IDs. |

```bash
jji mentions-mark-read --ids 10,11,12
```

**Return value/effect:** Marks the requested mentions as read. Use `--json` to inspect the server response.

> **Warning:** Every ID must be a positive integer.

### `jji mentions-mark-all-read`
Marks all mentions as read.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only global options. |

```bash
jji mentions-mark-all-read
```

**Return value/effect:** Marks all mentions as read. Use `--json` to inspect the server response.

## Issues and integrations
### `jji preview-issue JOB_ID`
Previews generated issue content for GitHub or Jira.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID. |
| `--test`, `-t` | string | — | Failure test name. |
| `--type` | string | — | Issue target: `github` or `jira`. |
| `--child-job` | string | `""` | Child job name. |
| `--child-build` | integer | `0` | Child build number. |
| `--include-links` | boolean | `false` | Include full URLs in the generated body. |
| `--ai-provider` | string | `""` | AI provider override for issue text generation. |
| `--ai-model` | string | `""` | AI model override for issue text generation. |
| `--github-token` | string | profile `github_token` or empty | GitHub token override. Used only with `--type github`. |
| `--github-repo-url` | string | profile `github_repo_url` or empty | GitHub repository URL override. Used only with `--type github`. |
| `--jira-token` | string | profile `jira_token` or empty | Jira token override. Used only with `--type jira`. |
| `--jira-email` | string | profile `jira_email` or empty | Jira email override. Used only with `--type jira`. |
| `--jira-project-key` | string | profile `jira_project_key` or empty | Jira project key override. Used only with `--type jira`. |
| `--jira-security-level` | string | profile `jira_security_level` or empty | Jira security level name override. Used only with `--type jira`. |

```bash
jji preview-issue job-123 --test tests.e2e.test_login --type github --include-links
```

**Return value/effect:** Default output prints the generated title, body, and any returned similar issues. JSON mode returns the full preview object.

> **Note:** GitHub credential options are ignored for `--type jira`. Jira credential options are ignored for `--type github`.

### `jji create-issue JOB_ID`
Creates a GitHub issue or Jira bug from a stored failure.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID. |
| `--test`, `-t` | string | — | Failure test name. |
| `--type` | string | — | Issue target: `github` or `jira`. |
| `--title` | string | — | Issue title. |
| `--body` | string | — | Issue body. |
| `--child-job` | string | `""` | Child job name. |
| `--child-build` | integer | `0` | Child build number. |
| `--github-token` | string | profile `github_token` or empty | GitHub token override. Used only with `--type github`. |
| `--github-repo-url` | string | profile `github_repo_url` or empty | GitHub repository URL override. Used only with `--type github`. |
| `--jira-token` | string | profile `jira_token` or empty | Jira token override. Used only with `--type jira`. |
| `--jira-email` | string | profile `jira_email` or empty | Jira email override. Used only with `--type jira`. |
| `--jira-project-key` | string | profile `jira_project_key` or empty | Jira project key override. Used only with `--type jira`. |
| `--jira-security-level` | string | profile `jira_security_level` or empty | Jira security level name override. Used only with `--type jira`. |

```bash
jji create-issue \
  job-123 \
  --test tests.e2e.test_login \
  --type jira \
  --title "tests.e2e.test_login fails on periodic-e2e" \
  --body "Failure details..."
```

**Return value/effect:** Default output prints the created issue key/number, URL, and any created JJI comment ID. JSON mode returns the full creation response.

### `jji validate-token TOKEN_TYPE`
Validates a GitHub or Jira token.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `TOKEN_TYPE` | string | — | Token type: `github` or `jira`. |
| `--token` | string | prompt | Token value. The CLI prompts and hides input when omitted. |
| `--email` | string | `""` | Jira email value, used with Jira validation when needed. |

```bash
jji validate-token github --token "$GITHUB_TOKEN"
```

**Return value/effect:** Prints `Valid` on success. Invalid tokens exit non-zero. JSON mode returns the full validation payload.

### `jji push-reportportal JOB_ID`
Pushes JJI classifications into Report Portal.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_ID` | string | — | Analysis job ID. |
| `--child-job-name` | string | unset | Child job name for pipeline child pushes. |
| `--child-build-number` | integer | unset | Child build number for pipeline child pushes. |

```bash
jji push-reportportal job-123
```

**Return value/effect:** Default output prints pushed count, optional launch ID, unmatched tests, and error count/details. JSON mode returns the full push response.

> **Note:** Hidden alias: `jji push-rp JOB_ID`.

## Auth and admin
### `jji auth login`
Validates admin credentials against the server.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--username`, `-u` | string | — | Admin username. |
| `--api-key`, `-k` | string | — | Admin API key. |

```bash
jji auth login --username admin --api-key "$JJI_API_KEY"
```

**Return value/effect:** Default output prints username, role, and admin status. JSON mode returns the full auth response.

> **Note:** This command does not persist credentials to `config.toml`.

### `jji auth logout`
Calls the server logout endpoint.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only global options. |

```bash
jji auth logout
```

**Return value/effect:** Logs out the current server session. Use `--json` to inspect the server response.

### `jji auth whoami`
Shows the current authenticated user.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only global options. |

```bash
jji auth whoami
```

**Return value/effect:** Default output prints `username`, `role`, and `is_admin`. JSON mode returns the full auth payload.

### `jji admin users list`
Lists all known users.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only global options. |

```bash
jji admin users list
```

**Return value/effect:** Default output prints a user table with role, creation time, and last-seen time. JSON mode returns the full user list payload.

### `jji admin users create USERNAME`
Creates a new admin user.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `USERNAME` | string | — | Username for the new admin user. |

```bash
jji admin users create newadmin
```

**Return value/effect:** Default output prints the created username and the new API key. JSON mode returns the full response.

> **Warning:** The new API key is only shown when the command runs.

### `jji admin users delete USERNAME`
Deletes an admin user.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `USERNAME` | string | — | Username to delete. |
| `--force`, `-f` | boolean | `false` | Skip the confirmation prompt. |

```bash
jji admin users delete oldadmin --force
```

**Return value/effect:** Default output prints `Deleted admin user: ...`. JSON mode returns the raw delete response.

### `jji admin users rotate-key USERNAME`
Rotates one admin user's API key.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `USERNAME` | string | — | Username whose key should be rotated. |

```bash
jji admin users rotate-key myuser
```

**Return value/effect:** Default output prints the username and new API key. JSON mode returns the full rotation response.

> **Warning:** The rotated API key is only shown when the command runs.

### `jji admin users change-role USERNAME ROLE`
Changes a user's role.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `USERNAME` | string | — | Username to modify. |
| `ROLE` | string | — | New role: `admin` or `user`. |

```bash
jji admin users change-role myuser admin
```

**Return value/effect:** Default output prints the updated role. Promoting to `admin` also prints a newly generated API key when returned. JSON mode returns the full response.

> **Warning:** Promotion-generated API keys are only shown when the command runs.

### `jji admin token-usage`
Reports AI token and cost usage.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--period` | string | unset | Preset range: `today`, `week`, `month`, or `all`. |
| `--start-date` | string | unset | Start date in `YYYY-MM-DD` format. |
| `--end-date` | string | unset | End date in `YYYY-MM-DD` format. |
| `--provider` | string | unset | Filter by AI provider. |
| `--model` | string | unset | Filter by AI model. |
| `--call-type` | string | unset | Filter by call type. |
| `--group-by` | string | unset | Group by `provider`, `model`, `call_type`, `day`, `week`, `month`, or `job`. |
| `--job-id` | string | unset | Switch to per-job token usage for one analysis job. |
| `--format` | string | `table` | Output format: `table`, `json`, or `csv`. |

```bash
jji admin token-usage --group-by provider
```

**Return value/effect:** With no filters, the command prints a summary dashboard (`Today`, `This Week`, `This Month`, and top models). With filters or a period, it prints aggregated totals and an optional breakdown. With `--job-id`, it prints per-call records for one job.

> **Note:** `--job-id` switches to per-job mode.
>


> **Note:** `--period today|week|month` sets the start date automatically.
>


> **Warning:** Invalid `--period` or `--format` values exit with an error.

## Metadata
### `jji metadata list`
Lists job metadata records.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--team` | string | `""` | Filter by team. |
| `--tier` | string | `""` | Filter by tier. |
| `--version` | string | `""` | Filter by version. |
| `--label`, `-l` | string list | empty | Filter by one or more labels. Repeat the option for multiple labels. |

```bash
jji metadata list --team platform --label nightly
```

**Return value/effect:** Default output prints a metadata table with job name, team, tier, version, and labels. JSON mode returns the full array.

### `jji metadata get JOB_NAME`
Shows metadata for one job.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_NAME` | string | — | Job name to look up. |

```bash
jji metadata get periodic-e2e
```

**Return value/effect:** Default output prints the metadata row for the selected job. JSON mode returns the full object.

### `jji metadata set JOB_NAME`
Creates or updates metadata for one job.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_NAME` | string | — | Job name to update. |
| `--team` | string | `""` | Team value. |
| `--tier` | string | `""` | Tier value. |
| `--version` | string | `""` | Version value. |
| `--label`, `-l` | string list | empty | Label values. Repeat to set multiple labels. |

```bash
jji metadata set periodic-e2e --team platform --tier critical --label nightly
```

**Return value/effect:** Default output prints `Metadata set for ...`. JSON mode returns the full stored metadata object.

### `jji metadata delete JOB_NAME`
Deletes metadata for one job.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_NAME` | string | — | Job name to delete metadata for. |

```bash
jji metadata delete periodic-e2e
```

**Return value/effect:** Default output prints `Metadata deleted for ...`. JSON mode returns the raw delete response.

### `jji metadata import FILE_PATH`
Bulk imports metadata from a JSON or YAML file.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `FILE_PATH` | string | — | Path to a JSON file or a `.yaml`/`.yml` file containing an array of metadata objects. |

```bash
jji metadata import ./job-metadata.yaml
```

**Return value/effect:** Default output prints `Imported N metadata entries.` JSON mode returns the raw bulk-update response.

> **Note:** JSON is used for non-YAML extensions.
>


> **Warning:** The file must contain an array of objects.

### `jji metadata rules`
Lists configured metadata auto-assignment rules.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only global options. |

```bash
jji metadata rules
```

**Return value/effect:** Default output prints the rules file path when returned, plus a numbered rule list. JSON mode returns the full rules object.

### `jji metadata preview JOB_NAME`
Previews metadata rule matching for one job name.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `JOB_NAME` | string | — | Job name to test against the configured rules. |

```bash
jji metadata preview test-smoke
```

**Return value/effect:** Default output prints the matched metadata or `No rules matched ...`. JSON mode returns the full preview result.

## Config commands
### `jji config` / `jji config show`
Shows the current CLI config file and configured profiles.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only config file state. |

```bash
jji config
```

**Return value/effect:** Prints the config file path, default server, and configured server list. If the config file does not exist, the command prints the expected path and a starter snippet.

### `jji config servers`
Lists configured server profiles.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| none | — | — | Uses only config file state. |

```bash
jji config servers
```

**Return value/effect:** Default output prints a table of profile names, URLs, usernames, SSL behavior, and default-marker state. JSON mode returns an object keyed by server name.

### `jji config completion [SHELL]`
Prints shell-completion setup instructions.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `SHELL` | string | `zsh` | Shell type: `bash` or `zsh`. |

```bash
jji config completion bash
```

**Return value/effect:** Prints the shell snippet that evaluates `jji --show-completion ...` for the selected shell.

> **Warning:** Only `bash` and `zsh` are supported.

## Related Pages

- [Analyze a Jenkins Job](analyze-a-jenkins-job.html)
- [Investigate Failure History](investigate-failure-history.html)
- [Copy Common Analysis Recipes](copy-common-analysis-recipes.html)
- [REST API Reference](rest-api-reference.html)
- [Configuration and Environment Reference](configuration-and-environment-reference.html)

---

Source: rest-api-reference.md

# REST API Reference

> **Note:** When a request field says `server default`, omitting it uses the server's configured value. See [Configuration and Environment Reference](configuration-and-environment-reference.html) for the server-side defaults and environment variables.
>


> **Note:** This page covers the live analysis, history, comments, auth-state, notifications, metadata, and admin APIs. For issue preview/create endpoints, see [Create GitHub Issues and Jira Bugs](create-github-issues-and-jira-bugs.html). For Report Portal endpoints, see [Push Classifications to Report Portal](push-classifications-to-report-portal.html). For deployment health and metrics, see [Copy Common Deployment Recipes](copy-common-deployment-recipes.html).

## Common conventions
- Examples use `http://localhost:8000`.
- Admin-only endpoints return `403 Forbidden` without admin access.
- Current-user endpoints return `401 Unauthorized` when no current username is available.
- Non-admin write endpoints can also return `403 Forbidden` when the server allow list rejects the caller.
- Validation errors return `422 Unprocessable Entity` with a FastAPI-style error array.
- For `GET /results/{job_id}`, send `Accept: application/json` when you want API JSON instead of the browser UI route behavior.

```json
{
  "detail": [
    {
      "type": "int_parsing",
      "loc": ["body", "build_number"],
      "msg": "Input should be a valid integer",
      "input": "not-a-number"
    }
  ]
}
```

### Queued job response
Returned by `POST /analyze` and `POST /re-analyze/{job_id}`.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `status` | string | n/a | Always `queued`. |
| `job_id` | string | n/a | Server-generated job UUID. |
| `message` | string | n/a | Polling hint that includes the result path. |
| `base_url` | string | `""` | Trusted public base URL when configured; otherwise empty. |
| `result_url` | string | n/a | Relative or absolute URL for the stored result. |

```json
{
  "status": "queued",
  "job_id": "8d0f0a65-6b52-4eb9-8dc5-9e3c38f6f6a6",
  "message": "Analysis job queued. Poll /results/8d0f0a65-6b52-4eb9-8dc5-9e3c38f6f6a6 for status.",
  "base_url": "",
  "result_url": "/results/8d0f0a65-6b52-4eb9-8dc5-9e3c38f6f6a6"
}
```

### Stored result envelope
Returned by `GET /results/{job_id}`.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | n/a | Analysis job ID. |
| `jenkins_url` | string | `""` | Jenkins build URL for Jenkins-backed analyses. |
| `status` | string | n/a | Top-level job state: `pending`, `waiting`, `running`, `completed`, or `failed`. |
| `result` | object \| null | n/a | Stored result payload. Secrets are stripped from `request_params` before response. |
| `created_at` | string | n/a | Job creation timestamp. |
| `completed_at` | string \| null | `null` | Completion timestamp when available. |
| `analysis_started_at` | string \| null | `null` | Analysis-start timestamp when available. |
| `base_url` | string | `""` | Trusted public base URL when configured; otherwise empty. |
| `result_url` | string | n/a | Relative or absolute result URL. |
| `capabilities` | object | n/a | Same capability flags returned by `GET /api/capabilities`. |

```json
{
  "job_id": "8d0f0a65-6b52-4eb9-8dc5-9e3c38f6f6a6",
  "jenkins_url": "https://jenkins.example.com/job/my-pipeline/42/",
  "status": "completed",
  "result": {
    "job_name": "my-pipeline",
    "build_number": 42,
    "summary": "1 failure analyzed",
    "ai_provider": "claude",
    "ai_model": "opus-4",
    "failures": [],
    "request_params": {
      "ai_provider": "claude",
      "ai_model": "opus-4",
      "tests_repo_url": "https://github.com/acme/tests",
      "tests_repo_ref": ""
    }
  },
  "created_at": "2026-04-27 09:20:00",
  "completed_at": "2026-04-27 09:21:10",
  "analysis_started_at": "2026-04-27 09:20:05",
  "base_url": "",
  "result_url": "/results/8d0f0a65-6b52-4eb9-8dc5-9e3c38f6f6a6",
  "capabilities": {
    "github_issues_enabled": true,
    "jira_issues_enabled": true,
    "server_github_token": true,
    "server_jira_token": true,
    "server_jira_email": true,
    "server_jira_project_key": "ACME",
    "reportportal": false,
    "reportportal_project": ""
  }
}
```

## Analysis
> **Note:** For task-oriented usage and option combinations, see [Analyze a Jenkins Job](analyze-a-jenkins-job.html), [Customize AI Analysis](customize-ai-analysis.html), and [Copy Common Analysis Recipes](copy-common-analysis-recipes.html).

### Shared analysis override fields
Used by `POST /analyze`, `POST /analyze-failures`, and `POST /re-analyze/{job_id}`.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `tests_repo_url` | string \| null | server default | Repository URL cloned for analysis context. A `:ref` suffix is accepted. |
| `ai_provider` | string | server default | AI provider: `claude`, `gemini`, or `cursor`. |
| `ai_model` | string \| null | server default | AI model identifier. |
| `enable_jira` | boolean \| null | auto | Enables or disables Jira match enrichment for this request. |
| `ai_cli_timeout` | integer \| null | server default | AI CLI timeout in minutes. |
| `jira_url` | string \| null | server default | Jira base URL override. |
| `jira_email` | string \| null | server default | Jira Cloud email override. |
| `jira_api_token` | string \| null | server default | Jira Cloud API token override. |
| `jira_pat` | string \| null | server default | Jira Server/DC PAT override. |
| `jira_project_key` | string \| null | server default | Jira project key override. |
| `jira_ssl_verify` | boolean \| null | server default | Jira SSL verification override. |
| `jira_max_results` | integer \| null | server default | Max Jira matches to fetch. |
| `raw_prompt` | string \| null | none | Extra prompt text appended for this request. |
| `github_token` | string \| null | server default | GitHub token used for private-repo comment enrichment. |
| `peer_ai_configs` | array<object> \| null | server default | Peer review AI configs. Use `[]` to disable peer analysis for this request. |
| `peer_analysis_max_rounds` | integer | server default | Max peer-debate rounds. Only applied when the field is present. |
| `additional_repos` | array<object> \| null | server default | Additional repositories cloned for AI context. Use `[]` to disable server defaults for this request. |

#### `peer_ai_configs[]`

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `ai_provider` | string | required | `claude`, `gemini`, or `cursor`. |
| `ai_model` | string | required | Non-blank model identifier. |

#### `additional_repos[]`

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | string | required | Unique directory name used inside the analysis workspace. |
| `url` | string | required | Repository URL to clone. |
| `ref` | string | `""` | Branch or tag to check out. Empty string means the remote default branch. |
| `token` | string \| null | `null` | Clone token for private repositories. |

> **Tip:** `additional_repos[].name` must be unique, must not contain path separators, must not contain `..`, and must not start with `.`.

```json
{
  "tests_repo_url": "https://github.com/acme/tests:main",
  "ai_provider": "claude",
  "ai_model": "opus-4",
  "peer_ai_configs": [
    {
      "ai_provider": "gemini",
      "ai_model": "2.5-pro"
    }
  ],
  "peer_analysis_max_rounds": 2,
  "additional_repos": [
    {
      "name": "service",
      "url": "https://github.com/acme/service",
      "ref": "main"
    }
  ]
}
```

### `/analyze` body fields

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_name` | string | required | Jenkins job name. Folder-style names such as `folder/subfolder/job` are supported. |
| `build_number` | integer | required | Jenkins build number. |
| `force` | boolean | server default | Forces analysis even when the Jenkins build succeeded. |
| `wait_for_completion` | boolean | server default | Waits for the Jenkins build to finish before analysis. |
| `poll_interval_minutes` | integer | server default | Poll interval, in minutes, when waiting for build completion. |
| `max_wait_minutes` | integer | server default | Max wait time in minutes. `0` means no limit. |
| `jenkins_url` | string \| null | server default | Jenkins base URL override. |
| `jenkins_user` | string \| null | server default | Jenkins username override. |
| `jenkins_password` | string \| null | server default | Jenkins password or API token override. |
| `jenkins_ssl_verify` | boolean \| null | server default | Jenkins SSL verification override. |
| `jenkins_timeout` | integer \| null | server default | Jenkins API timeout in seconds. |
| `jenkins_artifacts_max_size_mb` | integer \| null | server default | Max artifact payload size downloaded for AI context. |
| `get_job_artifacts` | boolean \| null | server default | Enables or disables artifact download for this request. |

```json
{
  "job_name": "folder/my-pipeline",
  "build_number": 42,
  "ai_provider": "claude",
  "ai_model": "opus-4",
  "wait_for_completion": true,
  "poll_interval_minutes": 2
}
```

### `/analyze-failures` input objects

#### `failures[]`

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `test_name` | string | required | Fully qualified test name. |
| `error_message` | string | `""` | Failure message. |
| `stack_trace` | string | `""` | Full stack trace. |
| `duration` | number | `0.0` | Test duration in seconds. |
| `status` | string | `FAILED` | Failure status label. |

#### Request fields

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `failures` | array<object> \| null | none | Raw failures to analyze. Required unless `raw_xml` is provided. |
| `raw_xml` | string \| null | none | Raw JUnit XML. Required unless `failures` is provided. |

```json
{
  "failures": [
    {
      "test_name": "tests.test_auth.test_login",
      "error_message": "assert False",
      "stack_trace": "File tests/test_auth.py, line 42"
    }
  ],
  "ai_provider": "claude",
  "ai_model": "opus-4"
}
```

### `POST /analyze`
Submit a Jenkins build for asynchronous analysis.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| Body | object | required | Shared analysis override fields plus `/analyze` body fields. |

Return value/effect:
- `202 Accepted` returns the queued job response.
- `400 Bad Request` when AI provider/model configuration is missing or invalid.
- `403 Forbidden` when the allow list rejects the caller.
- `422 Unprocessable Entity` for validation errors.

```bash
curl -X POST http://localhost:8000/analyze \
  -H 'Content-Type: application/json' \
  -d '{
    "job_name": "folder/my-pipeline",
    "build_number": 42,
    "ai_provider": "claude",
    "ai_model": "opus-4"
  }'
```

### `POST /analyze-failures`
Analyze raw failures or raw JUnit XML without Jenkins polling.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| Body | object | required | Shared analysis override fields plus `/analyze-failures` input fields. |

Return value/effect:
- `200 OK` returns a direct-analysis result object with `job_id`, `status`, `summary`, `ai_provider`, `ai_model`, `failures`, optional `enriched_xml`, optional `token_usage`, plus `base_url` and `result_url`.
- `status` inside the response body is `completed` or `failed`.
- `400 Bad Request` for invalid XML or invalid/missing AI configuration.
- `422 Unprocessable Entity` when both `failures` and `raw_xml` are supplied, when neither is supplied, or when nested validation fails.
- If `raw_xml` contains no failures, the endpoint still returns `200` with `status: "completed"` and the original XML in `enriched_xml`.

```bash
curl -X POST http://localhost:8000/analyze-failures \
  -H 'Content-Type: application/json' \
  -d '{
    "failures": [
      {
        "test_name": "tests.test_auth.test_login",
        "error_message": "assert False",
        "stack_trace": "File tests/test_auth.py, line 42"
      }
    ],
    "ai_provider": "claude",
    "ai_model": "opus-4"
  }'
```

### `POST /re-analyze/{job_id}`
Queue a fresh analysis for an existing result, reusing the original stored request parameters and applying any override fields supplied in the body.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | required | Existing analysis job ID to reuse as the source request. |
| Body | object | `{}` | Shared analysis override fields only. Omitted fields reuse the stored request parameters. |

Return value/effect:
- `202 Accepted` returns the queued job response for the new job ID.
- `400 Bad Request` when the stored result has no reusable `request_params` or cannot be reconstructed.
- `404 Not Found` when the source `job_id` does not exist.
- `403 Forbidden` when the allow list rejects the caller.
- `422 Unprocessable Entity` for override-field validation errors.

```bash
curl -X POST http://localhost:8000/re-analyze/8d0f0a65-6b52-4eb9-8dc5-9e3c38f6f6a6 \
  -H 'Content-Type: application/json' \
  -d '{
    "ai_provider": "gemini",
    "ai_model": "2.5-pro"
  }'
```

### `GET /results/{job_id}`
Fetch the stored result envelope for a queued, running, completed, or failed job.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | required | Analysis job ID. |
| `Accept` header | string | `application/json` | Use `application/json` for API JSON responses. |

Return value/effect:
- `200 OK` returns the stored result envelope when the job is completed or failed.
- `202 Accepted` returns the same envelope shape while the job is still `pending`, `waiting`, or `running`.
- `404 Not Found` when the job does not exist.
- `result.request_params` is included when available, but secret fields and `additional_repos[].token` are removed from the response.

> **Note:** Browser-style HTML requests can redirect to UI routes instead of returning JSON.

```bash
curl http://localhost:8000/results/8d0f0a65-6b52-4eb9-8dc5-9e3c38f6f6a6 \
  -H 'Accept: application/json'
```

### `GET /results`
List recent analysis jobs.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `limit` | integer | `50` | Max number of rows to return. Maximum `100`. |

Return value/effect:
- `200 OK` returns an array of recent result summaries.
- Each item includes `job_id`, `jenkins_url`, `status`, and `created_at`.
- `422 Unprocessable Entity` when `limit` exceeds `100` or fails validation.

```bash
curl 'http://localhost:8000/results?limit=10'
```

### `GET /api/dashboard`
List dashboard entries with summary fields extracted from stored results.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns an array of dashboard entries.
- Every item includes `job_id`, `jenkins_url`, `status`, `created_at`, `completed_at`, `analysis_started_at`, `reviewed_count`, and `comment_count`.
- Items can also include `job_name`, `build_number`, `failure_count`, `child_job_count`, `summary`, and `error` when those values exist in `result_json`.

```bash
curl http://localhost:8000/api/dashboard
```

### `GET /api/capabilities`
Return server-level feature and credential flags used by the UI.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns:
  - `github_issues_enabled`
  - `jira_issues_enabled`
  - `server_github_token`
  - `server_jira_token`
  - `server_jira_email`
  - `server_jira_project_key`
  - `reportportal`
  - `reportportal_project`

```bash
curl http://localhost:8000/api/capabilities
```

## Comments and review
> **Note:** For UI workflow details, see [Review and Classify Failures](review-and-classify-failures.html).

### `GET /results/{job_id}/comments`
Return all stored comments and review states for a job.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | required | Analysis job ID. |

Return value/effect:
- `200 OK` returns:
  - `comments`: array of comment objects with `id`, `job_id`, `test_name`, `child_job_name`, `child_build_number`, `comment`, `error_signature`, `username`, and `created_at`
  - `reviews`: object keyed by `test_name` for top-level failures, or `child_job_name#child_build_number::test_name` for child-job failures
- Review values include `reviewed`, `username`, and `updated_at`.

```bash
curl http://localhost:8000/results/job-123/comments
```

### `POST /results/{job_id}/comments`
Add a comment to one stored failure.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | required | Analysis job ID. |
| `test_name` | string | required | Failure test name. |
| `comment` | string | required | Comment body. |
| `child_job_name` | string | `""` | Child job name for nested failures. |
| `child_build_number` | integer | `0` | Child build scope. `0` acts as a wildcard when `child_job_name` is supplied. |

Return value/effect:
- `201 Created` returns `{ "id": <comment_id> }`.
- `400 Bad Request` when the test name is not present in the stored result or comment creation fails validation.
- `404 Not Found` when the job does not exist.
- `403 Forbidden` when the allow list rejects the caller.
- When push notifications are configured, `@mentions` in the comment trigger best-effort notification fan-out.

```bash
curl -X POST http://localhost:8000/results/job-123/comments \
  -H 'Content-Type: application/json' \
  -d '{
    "test_name": "tests.test_auth.test_login",
    "comment": "Opened ACME-123 for this failure."
  }'
```

### `DELETE /results/{job_id}/comments/{comment_id}`
Delete one comment.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | required | Analysis job ID. |
| `comment_id` | integer | required | Comment ID. |

Return value/effect:
- `200 OK` returns `{ "status": "deleted" }`.
- `401 Unauthorized` when no current username is available.
- `404 Not Found` when the comment is missing, or when a non-admin caller tries to delete a comment they do not own.
- `403 Forbidden` when the allow list rejects the caller.
- Admin callers can delete any comment for the job.

```bash
curl -X DELETE http://localhost:8000/results/job-123/comments/17
```

### `PUT /results/{job_id}/reviewed`
Set or clear the reviewed state for one failure.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | required | Analysis job ID. |
| `test_name` | string | required | Failure test name. |
| `reviewed` | boolean | required | `true` marks the failure reviewed; `false` clears it. |
| `child_job_name` | string | `""` | Child job name for nested failures. |
| `child_build_number` | integer | `0` | Child build scope. `0` acts as a wildcard when `child_job_name` is supplied. |

Return value/effect:
- `200 OK` returns `{ "status": "ok", "reviewed_by": "<username-or-empty>" }`.
- `400 Bad Request` when the test is not present in the stored result.
- `404 Not Found` when the job does not exist.
- `403 Forbidden` when the allow list rejects the caller.

```bash
curl -X PUT http://localhost:8000/results/job-123/reviewed \
  -H 'Content-Type: application/json' \
  -d '{
    "test_name": "tests.test_auth.test_login",
    "reviewed": true
  }'
```

### `GET /results/{job_id}/review-status`
Return dashboard-style review counts for one job.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | required | Analysis job ID. |

Return value/effect:
- `200 OK` returns:
  - `total_failures`
  - `reviewed_count`
  - `comment_count`
- If the job has no stored result, the counts are `0`.

```bash
curl http://localhost:8000/results/job-123/review-status
```

### `POST /results/{job_id}/enrich-comments`
Resolve live status information for GitHub pull requests, GitHub issues, and Jira keys found inside stored comments.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | required | Analysis job ID. |

Return value/effect:
- `200 OK` returns `{ "enrichments": { ... } }`.
- `enrichments` is keyed by comment ID string.
- Each value is an array of objects with:
  - `type`: `github_pr`, `github_issue`, or `jira`
  - `key`: tracker-specific identifier such as `owner/repo#123` or `ACME-123`
  - `status`: tracker status string
- `403 Forbidden` when the allow list rejects the caller.

```bash
curl -X POST http://localhost:8000/results/job-123/enrich-comments
```

### `PUT /results/{job_id}/override-classification`
Override the primary classification for one failure group inside a stored result.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | required | Analysis job ID. |
| `test_name` | string | required | Failure test name used to identify the group. |
| `classification` | string | required | One of `CODE ISSUE`, `PRODUCT BUG`, or `INFRASTRUCTURE`. |
| `child_job_name` | string | `""` | Child job name for nested failures. |
| `child_build_number` | integer | `0` | Child build number. When `child_job_name` is set for this endpoint, a non-zero build number is required. |

Return value/effect:
- `200 OK` returns `{ "status": "ok", "classification": "<value>" }`.
- The override is applied to all failures in the same error-signature group within the job.
- `400 Bad Request` when the job exists but the target failure cannot be resolved, or when child-job scoping is invalid.
- `404 Not Found` when the job does not exist.
- `403 Forbidden` when the allow list rejects the caller.
- `422 Unprocessable Entity` when `classification` is not one of the allowed values.

```bash
curl -X PUT http://localhost:8000/results/job-123/override-classification \
  -H 'Content-Type: application/json' \
  -d '{
    "test_name": "tests.test_auth.test_login",
    "classification": "PRODUCT BUG"
  }'
```

## History
> **Note:** For investigative workflows and interpretation guidance, see [Investigate Failure History](investigate-failure-history.html).

### `GET /history/failures`
List failure-history rows with pagination and optional filters.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `search` | string | `""` | Free-text search across `test_name`, `error_message`, and `job_name`. |
| `job_name` | string | `""` | Exact `job_name` filter. |
| `classification` | string | `""` | Exact classification filter. |
| `limit` | integer | `50` | Max rows to return. Maximum `200`. |
| `offset` | integer | `0` | Number of rows to skip. Minimum `0`. |

Return value/effect:
- `200 OK` returns:
  - `failures`: array of rows with `id`, `job_id`, `job_name`, `build_number`, `test_name`, `error_message`, `error_signature`, `classification`, `child_job_name`, `child_build_number`, and `analyzed_at`
  - `total`: total row count before pagination
- `422 Unprocessable Entity` for query validation errors.

```bash
curl 'http://localhost:8000/history/failures?job_name=my-pipeline&classification=FLAKY&limit=25&offset=0'
```

### `GET /history/test/{test_name}`
Return historical statistics and recent failure rows for one test.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `test_name` | string | required | Test name path parameter. |
| `limit` | integer | `20` | Max recent runs to return. Maximum `100`. |
| `job_name` | string | `""` | Exact job-name filter. |
| `exclude_job_id` | string | `""` | Excludes rows from one analysis job. |

Return value/effect:
- `200 OK` returns:
  - `test_name`
  - `total_runs`
  - `failures`
  - `passes`
  - `failure_rate`
  - `first_seen`
  - `last_seen`
  - `last_classification`
  - `classifications`
  - `recent_runs`
  - `comments`
  - `consecutive_failures`
  - `note`
- `passes` and `failure_rate` can be `null` when no `job_name` filter is supplied and the endpoint cannot compute a pass denominator.
- When no matching history exists, the response still returns `200` with zeroed counts and empty collections.

```bash
curl 'http://localhost:8000/history/test/tests.test_auth.test_login?limit=10&job_name=my-pipeline'
```

### `GET /history/search`
Find failures that share one error signature.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `signature` | string | required | Error-signature hash to search. |
| `exclude_job_id` | string | `""` | Excludes rows from one analysis job. |

Return value/effect:
- `200 OK` returns:
  - `signature`
  - `total_occurrences`
  - `unique_tests`
  - `tests`: array of `{ "test_name": "...", "occurrences": <int> }`
  - `last_classification`
  - `comments`: array of `{ "comment": "...", "username": "...", "created_at": "..." }`
- `422 Unprocessable Entity` when `signature` is omitted.

```bash
curl 'http://localhost:8000/history/search?signature=abc123def456'
```

### `GET /history/stats/{job_name}`
Return aggregate failure statistics for one Jenkins job name.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_name` | string | required | Jenkins job name path parameter. |
| `exclude_job_id` | string | `""` | Excludes rows from one analysis job. |

Return value/effect:
- `200 OK` returns:
  - `job_name`
  - `total_builds_analyzed`
  - `builds_with_failures`
  - `overall_failure_rate`
  - `most_common_failures`: array of `{ "test_name": "...", "count": <int>, "classification": "..." }`
  - `recent_trend`: `stable`, `improving`, or `worsening`
- If no history exists for the job, the endpoint still returns `200` with zeroed counters and `recent_trend: "stable"`.

```bash
curl 'http://localhost:8000/history/stats/my-pipeline'
```

### `POST /history/classify`
Create a history classification record for one test.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `test_name` | string | required | Test name to classify. |
| `classification` | string | required | One of `FLAKY`, `REGRESSION`, `INFRASTRUCTURE`, `KNOWN_BUG`, or `INTERMITTENT`. |
| `reason` | string | `""` | Free-text reason. |
| `job_name` | string | `""` | Job name scope stored on the classification record. |
| `references` | string | `""` | Reference text such as Jira keys or URLs. |
| `job_id` | string | required | Analysis job ID that the classification is tied to. |
| `child_build_number` | integer | `0` | Child build scope. `0` acts as the wildcard value. |

Return value/effect:
- `201 Created` returns `{ "id": <classification_id> }`.
- `400 Bad Request` when `test_name` is blank or when `KNOWN_BUG` is sent without non-empty `references`.
- `403 Forbidden` when the allow list rejects the caller.
- `422 Unprocessable Entity` for validation errors.

> **Warning:** `classification: "KNOWN_BUG"` requires a non-empty `references` value.

```bash
curl -X POST http://localhost:8000/history/classify \
  -H 'Content-Type: application/json' \
  -d '{
    "test_name": "tests.test_auth.test_login",
    "classification": "FLAKY",
    "reason": "Intermittent network timeout",
    "job_id": "job-123"
  }'
```

### `GET /history/classifications`
List visible primary classification records.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `test_name` | string | `""` | Exact test-name filter. |
| `classification` | string | `""` | Exact classification filter. |
| `job_name` | string | `""` | Exact job-name filter. |
| `parent_job_name` | string | `""` | Exact parent-job-name filter. |
| `job_id` | string | `""` | Exact job-ID filter. |

Return value/effect:
- `200 OK` returns `{ "classifications": [...] }`.
- Each classification row includes `id`, `test_name`, `job_name`, `parent_job_name`, `classification`, `reason`, `references_info`, `created_by`, `job_id`, `child_build_number`, and `created_at`.
- This endpoint returns the visible primary-domain records used for stored failure overrides.

```bash
curl 'http://localhost:8000/history/classifications?classification=PRODUCT%20BUG'
```

## Auth state and saved user tokens
> **Note:** This section documents the live auth-state and saved-token endpoints. For user bootstrap, API keys, and role-management workflows, see [Manage Users, Access, and Token Usage](manage-users-access-and-token-usage.html). For tracker-specific profile workflows, see [Configure Your Profile and Notifications](configure-your-profile-and-notifications.html).

### `GET /api/auth/me`
Return the current request's resolved user identity.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns:
  - `username`
  - `role`
  - `is_admin`
- The endpoint also returns `200` when no current user exists; in that case `username` is empty and `is_admin` is `false`.

```javascript
const res = await fetch("/api/auth/me", { credentials: "include" });
console.log(await res.json());
```

### `POST /api/auth/logout`
Clear the current admin-auth state.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns `{ "ok": true }`.

```javascript
const res = await fetch("/api/auth/logout", {
  method: "POST",
  credentials: "include"
});
console.log(await res.json());
```

### `GET /api/user/tokens`
Return the current user's saved tracker tokens.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns `{ "github_token": "...", "jira_email": "...", "jira_token": "..." }`.
- If the current username is unknown to the database, the endpoint still returns `200` with empty strings for all three fields.
- `401 Unauthorized` when no current user exists.

```javascript
const res = await fetch("/api/user/tokens", { credentials: "include" });
console.log(await res.json());
```

### `PUT /api/user/tokens`
Merge non-empty token fields into the current user's saved token record.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `github_token` | string | omitted | GitHub personal access token. |
| `jira_email` | string | omitted | Jira Cloud email. |
| `jira_token` | string | omitted | Jira token. |

Return value/effect:
- `200 OK` returns `{ "ok": true }`.
- Only non-empty fields in the request body are written.
- Omitted fields are left unchanged.
- `401 Unauthorized` when no current user exists.
- `404 Not Found` when the current username does not exist in the database.

> **Warning:** Blank-string values are not persisted as clears; only non-empty values are written.

```javascript
const res = await fetch("/api/user/tokens", {
  method: "PUT",
  credentials: "include",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    github_token: "ghp_example",
    jira_email: "alice@example.com",
    jira_token: "jira-example"
  })
});
console.log(await res.json());
```

## Notifications and mentions
> **Note:** For end-user setup and browser flow details, see [Configure Your Profile and Notifications](configure-your-profile-and-notifications.html).

### `GET /api/notifications/vapid-public-key`
Return the public VAPID key used by browser push subscriptions.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns `{ "vapid_public_key": "..." }`.
- `404 Not Found` when Web Push is not configured.
- `503 Service Unavailable` when push support is enabled but the VAPID keys are unavailable.

```javascript
const res = await fetch("/api/notifications/vapid-public-key");
console.log(await res.json());
```

### `POST /api/notifications/subscribe`
Register or update one push subscription for the current user.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `endpoint` | string | required | HTTPS push endpoint URL. Maximum length `2048`. |
| `p256dh_key` | string | required | Client public key. Maximum length `256`. |
| `auth_key` | string | required | Client auth secret. Maximum length `256`. |

Return value/effect:
- `200 OK` returns `{ "status": "subscribed" }`.
- The subscription is upserted by `endpoint`.
- The server keeps at most `10` subscriptions per user and drops the oldest extras.
- `401 Unauthorized` when no current user exists.
- `404 Not Found` when Web Push is not configured.
- `403 Forbidden` when the allow list rejects the caller.
- `422 Unprocessable Entity` when validation fails, including non-HTTPS endpoints.

```javascript
const res = await fetch("/api/notifications/subscribe", {
  method: "POST",
  credentials: "include",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    endpoint: "https://push.example.com/sub/abc123",
    p256dh_key: "p256dh-test",
    auth_key: "auth-test"
  })
});
console.log(await res.json());
```

### `POST /api/notifications/unsubscribe`
Remove one push subscription for the current user.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `endpoint` | string | required | HTTPS push endpoint URL. Maximum length `2048`. |

Return value/effect:
- `200 OK` returns `{ "status": "unsubscribed" }`.
- `401 Unauthorized` when no current user exists.
- `404 Not Found` when Web Push is not configured or when the endpoint is not owned by the current user.
- `403 Forbidden` when the allow list rejects the caller.
- `422 Unprocessable Entity` when validation fails, including non-HTTPS endpoints.

```javascript
const res = await fetch("/api/notifications/unsubscribe", {
  method: "POST",
  credentials: "include",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    endpoint: "https://push.example.com/sub/abc123"
  })
});
console.log(await res.json());
```

### `GET /api/users/mentions`
Return comments that mention the current user.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `offset` | integer | `0` | Pagination offset. Negative values are clamped to `0`. |
| `limit` | integer | `50` | Pagination size. Values above `200` are clamped to `200`. |
| `unread_only` | boolean-like string | `false` | Truthy values: `true`, `1`, `yes`. |

Return value/effect:
- `200 OK` returns:
  - `mentions`: array of mention objects with `id`, `job_id`, `test_name`, `child_job_name`, `child_build_number`, `comment`, `username`, `created_at`, and `is_read`
  - `total`
  - `unread_count`
- `401 Unauthorized` when no current user exists.
- `403 Forbidden` when the allow list rejects the caller.
- `400 Bad Request` when `offset` or `limit` is not an integer.

```javascript
const res = await fetch("/api/users/mentions?unread_only=true&limit=20", {
  credentials: "include"
});
console.log(await res.json());
```

### `POST /api/users/mentions/read`
Mark specific mention rows as read for the current user.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `comment_ids` | array<integer> | required | Non-empty list of comment IDs. Booleans are rejected. |

Return value/effect:
- `200 OK` returns `{ "ok": true }`.
- `401 Unauthorized` when no current user exists.
- `403 Forbidden` when the allow list rejects the caller.
- `400 Bad Request` when `comment_ids` is missing, empty, or contains non-integers.

```javascript
const res = await fetch("/api/users/mentions/read", {
  method: "POST",
  credentials: "include",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ comment_ids: [12, 15] })
});
console.log(await res.json());
```

### `POST /api/users/mentions/read-all`
Mark every unread mention as read for the current user.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns `{ "marked_read": <count> }`.
- `401 Unauthorized` when no current user exists.
- `403 Forbidden` when the allow list rejects the caller.

```javascript
const res = await fetch("/api/users/mentions/read-all", {
  method: "POST",
  credentials: "include"
});
console.log(await res.json());
```

### `GET /api/users/mentions/unread-count`
Return the unread-mention badge count for the current user.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns `{ "count": <int> }`.
- `401 Unauthorized` when no current user exists.
- `403 Forbidden` when the allow list rejects the caller.

```javascript
const res = await fetch("/api/users/mentions/unread-count", {
  credentials: "include"
});
console.log(await res.json());
```

### `GET /api/users/mentionable`
Return the list of usernames that can be mentioned.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns `{ "usernames": ["alice", "bob", ...] }`.
- `401 Unauthorized` when no current user exists.
- `403 Forbidden` when the allow list rejects the caller.

```javascript
const res = await fetch("/api/users/mentionable", {
  credentials: "include"
});
console.log(await res.json());
```

## Metadata
> **Note:** For end-user metadata workflows and examples, see [Organize Jobs with Metadata](organize-jobs-with-metadata.html).

### `GET /api/jobs/metadata`
List all stored job metadata, optionally filtered.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `team` | string | `""` | Exact team filter. |
| `tier` | string | `""` | Exact tier filter. |
| `version` | string | `""` | Exact version filter. |
| `label` | string (repeatable) | none | Label filter. Repeating the parameter requires all listed labels to be present. |

Return value/effect:
- `200 OK` returns an array of metadata objects with `job_name`, `team`, `tier`, `version`, and `labels`.

```bash
curl 'http://localhost:8000/api/jobs/metadata?team=platform&label=nightly&label=smoke'
```

### `GET /api/jobs/{job_name:path}/metadata`
Return one metadata object.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_name` | string | required | Jenkins job name. Path-style folder names are supported. |

Return value/effect:
- `200 OK` returns `{ "job_name": "...", "team": "...", "tier": "...", "version": "...", "labels": [...] }`.
- `404 Not Found` when no metadata exists for the job.

```bash
curl http://localhost:8000/api/jobs/folder/subfolder/my-job/metadata
```

### `PUT /api/jobs/{job_name:path}/metadata`
Create or update one metadata record.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_name` | string | required | Jenkins job name. |
| `team` | string \| null | omitted | Team value to write. |
| `tier` | string \| null | omitted | Tier value to write. |
| `version` | string \| null | omitted | Version value to write. |
| `labels` | array<string> | omitted | Label array to write. |

Return value/effect:
- `200 OK` returns the stored metadata object.
- Omitted fields are preserved from the existing record.
- `403 Forbidden` without admin access.

```bash
curl -X PUT http://localhost:8000/api/jobs/my-job/metadata \
  -H 'Authorization: Bearer <admin-bearer-token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "team": "platform",
    "tier": "critical",
    "labels": ["nightly", "smoke"]
  }'
```

### `DELETE /api/jobs/{job_name:path}/metadata`
Delete one metadata record.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_name` | string | required | Jenkins job name. |

Return value/effect:
- `200 OK` returns `{ "status": "deleted", "job_name": "..." }`.
- `404 Not Found` when no metadata exists for the job.
- `403 Forbidden` without admin access.

```bash
curl -X DELETE http://localhost:8000/api/jobs/my-job/metadata \
  -H 'Authorization: Bearer <admin-bearer-token>'
```

### `PUT /api/jobs/metadata/bulk`
Bulk upsert metadata records.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `items` | array<object> | required | Array of `1` to `1000` metadata rows. Each row has `job_name`, `team`, `tier`, `version`, and `labels`. |

Return value/effect:
- `200 OK` returns `{ "updated": <count> }`.
- `403 Forbidden` without admin access.
- `422 Unprocessable Entity` when validation fails or a row is missing `job_name`.

> **Warning:** Bulk import is a full row replace for each item. Optional fields omitted from an item are stored as `null` or `[]`, not preserved from an existing row.

```bash
curl -X PUT http://localhost:8000/api/jobs/metadata/bulk \
  -H 'Authorization: Bearer <admin-bearer-token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "items": [
      {
        "job_name": "job-a",
        "team": "alpha"
      },
      {
        "job_name": "job-b",
        "team": "beta",
        "labels": ["ci"]
      }
    ]
  }'
```

### `GET /api/jobs/metadata/rules`
Return the configured metadata-rule file name and normalized rule list.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns:
  - `rules_file`: basename of the configured rules file, or `null`
  - `rules`: normalized rule array
- Each rule can contain:
  - `pattern`
  - `team`
  - `tier`
  - `version`
  - `labels`

```bash
curl http://localhost:8000/api/jobs/metadata/rules
```

### `POST /api/jobs/metadata/rules/preview`
Preview the metadata that the current rule set would assign to one job name.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_name` | string | required | Jenkins job name to test against the loaded rules. |

Return value/effect:
- `200 OK` returns:
  - `job_name`
  - `matched`
  - `metadata`: object or `null`
- `422 Unprocessable Entity` when `job_name` is missing, blank, or not a string.

```bash
curl -X POST http://localhost:8000/api/jobs/metadata/rules/preview \
  -H 'Content-Type: application/json' \
  -d '{
    "job_name": "team-a/nightly"
  }'
```

### `GET /api/dashboard/filtered`
Return dashboard entries with attached metadata and optional metadata filters.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `team` | string | `""` | Exact team filter. |
| `tier` | string | `""` | Exact tier filter. |
| `version` | string | `""` | Exact version filter. |
| `label` | string (repeatable) | none | Label filter. Repeating the parameter requires all listed labels to be present. |

Return value/effect:
- `200 OK` returns the same dashboard entry shape as `GET /api/dashboard`.
- Every returned job also includes `metadata`, which is either a metadata object or `null`.
- Without filters, the endpoint returns all dashboard jobs with metadata attached.

```bash
curl 'http://localhost:8000/api/dashboard/filtered?team=platform&label=nightly'
```

## Admin
> **Note:** For admin workflows and operational guidance, see [Manage Users, Access, and Token Usage](manage-users-access-and-token-usage.html).

### `DELETE /api/results/bulk`
Delete multiple analysis jobs and their related data in one request.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_ids` | array<string> | required | Array of `1` to `500` job IDs. Duplicate IDs are de-duplicated while preserving order. |

Return value/effect:
- `200 OK` returns:
  - `deleted`: array of deleted job IDs
  - `failed`: array of `{ "job_id": "...", "reason": "..." }`
  - `total`: number of unique job IDs processed
- `403 Forbidden` without admin access.
- `422 Unprocessable Entity` for validation errors.

```bash
curl -X DELETE http://localhost:8000/api/results/bulk \
  -H 'Authorization: Bearer <admin-bearer-token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "job_ids": ["job-1", "job-2"]
  }'
```

### `DELETE /results/{job_id}`
Delete one analysis job and all related data.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | required | Analysis job ID. |

Return value/effect:
- `200 OK` returns `{ "status": "deleted", "job_id": "..." }`.
- `404 Not Found` when the job does not exist.
- `403 Forbidden` without admin access.

```bash
curl -X DELETE http://localhost:8000/results/job-123 \
  -H 'Authorization: Bearer <admin-bearer-token>'
```

### `GET /api/admin/token-usage`
Return aggregated AI token-usage totals with optional filters and grouping.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `start_date` | string | none | Lower bound for `created_at`. |
| `end_date` | string | none | Upper bound for `created_at`. A date-only value such as `2026-04-27` is expanded to the end of that day. |
| `ai_provider` | string | none | Exact provider filter. |
| `ai_model` | string | none | Exact model filter. |
| `call_type` | string | none | Exact call-type filter. |
| `group_by` | string | none | One of `provider`, `model`, `call_type`, `day`, `week`, `month`, or `job`. |

Return value/effect:
- `200 OK` returns:
  - `total_input_tokens`
  - `total_output_tokens`
  - `total_cache_read_tokens`
  - `total_cache_write_tokens`
  - `total_cost_usd`
  - `total_calls`
  - `total_duration_ms`
  - `breakdown`
- `breakdown` is empty when `group_by` is omitted.
- When present, each breakdown row includes `group_key`, `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_write_tokens`, `cost_usd`, `call_count`, and `avg_duration_ms`.
- `403 Forbidden` without admin access.
- `422 Unprocessable Entity` when `group_by` is not one of the supported values.

```bash
curl 'http://localhost:8000/api/admin/token-usage?group_by=provider&start_date=2026-04-01&end_date=2026-04-30' \
  -H 'Authorization: Bearer <admin-bearer-token>'
```

### `GET /api/admin/token-usage/summary`
Return dashboard-oriented token-usage summaries.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns:
  - `today`
  - `this_week`
  - `this_month`
  - `top_models`
  - `top_jobs`
- Period objects include `calls`, `tokens`, `input_tokens`, `output_tokens`, and `cost_usd`.
- `top_models` rows include `model`, `calls`, and `cost_usd`.
- `top_jobs` rows include `job_id`, `calls`, and `cost_usd`.
- `403 Forbidden` without admin access.

```bash
curl http://localhost:8000/api/admin/token-usage/summary \
  -H 'Authorization: Bearer <admin-bearer-token>'
```

### `GET /api/admin/token-usage/{job_id}`
Return raw token-usage records for one analysis job.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `job_id` | string | required | Analysis job ID. |

Return value/effect:
- `200 OK` returns:
  - `job_id`
  - `records`: array of rows with `id`, `job_id`, `created_at`, `ai_provider`, `ai_model`, `call_type`, `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_write_tokens`, `total_tokens`, `cost_usd`, `duration_ms`, `prompt_chars`, and `response_chars`
- `404 Not Found` when no token-usage rows exist for the job.
- `403 Forbidden` without admin access.

```bash
curl http://localhost:8000/api/admin/token-usage/job-123 \
  -H 'Authorization: Bearer <admin-bearer-token>'
```

### `POST /api/admin/users`
Create a new admin user and return its generated API key.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `username` | string | required | New admin username. Must be 2 to 50 characters, start with an alphanumeric character, and only contain letters, digits, `.`, `_`, or `-`. |

Return value/effect:
- `200 OK` returns `{ "username": "...", "api_key": "...", "role": "admin" }`.
- `400 Bad Request` when the username is invalid, already taken, or reserved.
- `403 Forbidden` without admin access.

```bash
curl -X POST http://localhost:8000/api/admin/users \
  -H 'Authorization: Bearer <admin-bearer-token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "username": "newadmin"
  }'
```

### `GET /api/admin/users`
List tracked users.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| None | n/a | n/a | No request parameters. |

Return value/effect:
- `200 OK` returns `{ "users": [...] }`.
- Each user row includes `id`, `username`, `role`, `created_at`, and `last_seen`.
- `403 Forbidden` without admin access.

```bash
curl http://localhost:8000/api/admin/users \
  -H 'Authorization: Bearer <admin-bearer-token>'
```

### `DELETE /api/admin/users/{username}`
Delete one admin user.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `username` | string | required | Admin username to delete. |

Return value/effect:
- `200 OK` returns `{ "deleted": "<username>" }`.
- `400 Bad Request` when the request would delete the caller's own account or the last admin user.
- `404 Not Found` when the named admin user does not exist.
- `403 Forbidden` without admin access.

```bash
curl -X DELETE http://localhost:8000/api/admin/users/oldadmin \
  -H 'Authorization: Bearer <admin-bearer-token>'
```

### `PUT /api/admin/users/{username}/role`
Change one tracked user's role.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `username` | string | required | Username to update. |
| `role` | string | required | Target role: `admin` or `user`. |

Return value/effect:
- `200 OK` returns `{ "username": "...", "role": "..." }`.
- When promoting to `admin`, the response also includes `api_key`.
- Demoting an admin to `user` removes their admin key and invalidates their active sessions.
- `400 Bad Request` when the caller targets themself, requests the current role, uses an invalid role, targets the reserved `admin` user, or attempts to demote the last admin.
- `404 Not Found` when the user does not exist.
- `403 Forbidden` without admin access.

```bash
curl -X PUT http://localhost:8000/api/admin/users/alice/role \
  -H 'Authorization: Bearer <admin-bearer-token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "role": "admin"
  }'
```

### `POST /api/admin/users/{username}/rotate-key`
Rotate or set an admin user's API key.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `username` | string | required | Admin username whose key will be rotated. |
| `new_key` | string \| null | auto-generate | Optional replacement key. Must be at least 16 characters when supplied. |

Return value/effect:
- `200 OK` returns `{ "username": "...", "new_api_key": "..." }`.
- Existing sessions for that admin user are invalidated.
- `400 Bad Request` for invalid JSON or invalid key input.
- `404 Not Found` when the admin user does not exist.
- `403 Forbidden` without admin access.

```bash
curl -X POST http://localhost:8000/api/admin/users/alice/rotate-key \
  -H 'Authorization: Bearer <admin-bearer-token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "new_key": "a-very-long-custom-admin-key"
  }'
```

## Related Pages

- [CLI Command Reference](cli-command-reference.html)
- [Analyze a Jenkins Job](analyze-a-jenkins-job.html)
- [Investigate Failure History](investigate-failure-history.html)
- [Manage Users, Access, and Token Usage](manage-users-access-and-token-usage.html)
- [Configuration and Environment Reference](configuration-and-environment-reference.html)

---

Source: configuration-and-environment-reference.md

# Configuration and Environment Reference

Server configuration uses environment variables on the server, profile fields in the `jji` client config file, and per-request override fields on analysis endpoints.

## Configuration Precedence

### Server-side analysis resolution

| Source | Scope | Effect |
| --- | --- | --- |
| Request body override fields | `POST /analyze`, `POST /analyze-failures` | Overrides server defaults for one request only. |
| Server environment variables | FastAPI server process | Supplies the server-wide defaults used when a request omits an override. |
| Application defaults | Built into the code | Used only when neither the request nor the environment provides a value. |

```bash
export AI_PROVIDER=claude
export AI_MODEL=sonnet
export JENKINS_TIMEOUT=30
```

```json
{
  "job_name": "folder/job-name",
  "build_number": 1042,
  "ai_provider": "gemini",
  "ai_model": "gemini-2.5-pro",
  "jenkins_timeout": 60
}
```

### CLI resolution

| Source | Scope | Effect |
| --- | --- | --- |
| CLI flags and `JJI_*` environment variables | Current `jji` invocation | Highest-priority client-side settings. |
| `config.toml` selected server profile | Current `jji` invocation | Supplies defaults for that named server. |
| `config.toml` `[defaults]` | Current `jji` invocation | Supplies shared defaults merged into every named server. |
| Remote server configuration | Server-side | Still applies after the CLI sends the request. |

> **Note:** If `--server` or `JJI_SERVER` is a full `http://` or `https://` URL, `jji` does not load any profile defaults from `config.toml` for that connection.

```bash
export JJI_SERVER=prod
export JJI_USERNAME=alice
jji --api-key "jji_example_admin_key" results list
```

## Server Environment Variables

> **Note:** Optional string settings treat blank or whitespace-only values as unset.

### Storage and Runtime

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `DB_PATH` | path | `/data/results.db` | Filesystem location of the SQLite database file. | The directory must exist and be writable; health checks report a database error if access fails. |
| `JJI_ENCRYPTION_KEY` | string | auto-generated file key | Secret used for encrypting sensitive values at rest and for hashing delegated admin API keys. | If unset, JJI creates and reuses a persistent key file under `$XDG_DATA_HOME/jji/.encryption_key` or `~/.local/share/jji/.encryption_key`. |
| `PORT` | integer | `8000` | HTTP port for the FastAPI server. | Must be between `1` and `65535`; the same value is used for Uvicorn and internal self-calls made by the AI workflow. |
| `DEBUG` | boolean | `false` | Debug mode toggle. | When `true`, the Python entry point enables Uvicorn reload. |
| `LOG_LEVEL` | string | `INFO` | Log level passed to application loggers. | Controls server log verbosity across the app. |
| `DEV_MODE` | boolean | `false` | Container development-mode toggle. | In the container entrypoint, `true` starts the Vite dev server on `5173` and adds Uvicorn reload flags. |
| `PUBLIC_BASE_URL` | URL | unset | Trusted public origin for absolute links. | When unset, JJI emits relative links and does not trust request host headers. |
| `XDG_DATA_HOME` | path | `~/.local/share` | Base directory for generated persistent data files. | Used for the fallback encryption key file and fallback VAPID key file. |
| `XDG_CONFIG_HOME` | path | `~/.config` | Base directory for user configuration files. | Sets the base path for `jji` config at `$XDG_CONFIG_HOME/jji/config.toml`. |

```bash
export DB_PATH="/srv/jji/state/results.sqlite3"
export JJI_ENCRYPTION_KEY="replace-with-a-stable-secret"
export PORT=8080
export PUBLIC_BASE_URL="https://jji.example.com"
export LOG_LEVEL=DEBUG
```

See [Copy Common Deployment Recipes](copy-common-deployment-recipes.html) for deployment examples.

### Access, Sessions, and Identity

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `ADMIN_KEY` | string | unset | Bootstrap secret for the built-in `admin` login path. | Enables initial admin sign-in with username `admin` at `/api/auth/login`. |
| `ALLOWED_USERS` | comma-separated string | unset | Allow list for users who can create or modify data. | Empty means open access; usernames are normalized to lowercase; admins bypass the list. |
| `SECURE_COOKIES` | boolean | `true` | Cookie security toggle. | Adds the `Secure` attribute to `jji_session` and `jji_username` cookies. |
| `TRUST_PROXY_HEADERS` | boolean | `false` | Reverse-proxy identity toggle. | Trusts `X-Forwarded-User` and mirrors it into the current request user and the `jji_username` cookie; `admin` is reserved and rejected from the proxy header. |

```bash
export ADMIN_KEY="replace-with-a-bootstrap-secret"
export ALLOWED_USERS="alice,bob,release-bot"
export SECURE_COOKIES=false
export TRUST_PROXY_HEADERS=true
```

### Jenkins Defaults

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `JENKINS_URL` | URL | unset | Default Jenkins base URL. | Used when `/analyze` omits `jenkins_url`. |
| `JENKINS_USER` | string | unset | Default Jenkins username. | Used when `/analyze` omits `jenkins_user`. |
| `JENKINS_PASSWORD` | string | unset | Default Jenkins password or API token. | Used when `/analyze` omits `jenkins_password`. |
| `JENKINS_SSL_VERIFY` | boolean | `true` | Jenkins TLS verification toggle. | `false` allows self-signed certificates. |
| `JENKINS_TIMEOUT` | integer | `30` | Jenkins API timeout in seconds. | Used for Jenkins API requests when the request omits `jenkins_timeout`. |
| `TESTS_REPO_URL` | string | unset | Default tests repository URL. | Used for repository context when the request omits `tests_repo_url`; accepts an optional `:ref` suffix. |
| `WAIT_FOR_COMPLETION` | boolean | `true` | Default wait behavior for `/analyze`. | When `true`, a queued analysis can remain in `waiting` until Jenkins finishes the build. |
| `POLL_INTERVAL_MINUTES` | integer | `2` | Default poll interval while waiting. | Controls how often JJI rechecks Jenkins status. |
| `MAX_WAIT_MINUTES` | integer | `0` | Default maximum wait duration. | `0` means no wait limit. |
| `FORCE_ANALYSIS` | boolean | `false` | Default force-analysis toggle. | Allows analysis to run even when Jenkins reports `SUCCESS`. |
| `GET_JOB_ARTIFACTS` | boolean | `true` | Default artifact-download toggle. | When `true`, JJI downloads build artifacts for analysis context. |
| `JENKINS_ARTIFACTS_MAX_SIZE_MB` | integer | `500` | Maximum total artifact download size, in MB. | Caps artifact collection per job. |

```bash
export JENKINS_URL="https://jenkins.example.com"
export JENKINS_USER="ci-bot"
export JENKINS_PASSWORD="replace-with-a-token"
export JENKINS_SSL_VERIFY=false
export TESTS_REPO_URL="https://gitlab.internal:8443/qa/tests:main"
export WAIT_FOR_COMPLETION=true
export POLL_INTERVAL_MINUTES=3
export MAX_WAIT_MINUTES=45
export GET_JOB_ARTIFACTS=true
export JENKINS_ARTIFACTS_MAX_SIZE_MB=750
```

### AI Defaults

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `AI_PROVIDER` | string | unset | Default AI provider. Valid values: `claude`, `gemini`, `cursor`. | Required unless the request body provides `ai_provider`. |
| `AI_MODEL` | string | unset | Default AI model identifier. | Required unless the request body provides `ai_model`. |
| `AI_CLI_TIMEOUT` | integer | `10` | Timeout for each provider CLI call, in minutes. | Applies to AI subprocess execution. |
| `PEER_AI_CONFIGS` | string | unset | Default peer set in `provider:model,provider:model` format. | Enables peer analysis when the request omits `peer_ai_configs`; blank disables the default. |
| `PEER_ANALYSIS_MAX_ROUNDS` | integer | `3` | Maximum peer debate rounds. | Must be between `1` and `10`. |
| `ADDITIONAL_REPOS` | string | unset | Default extra repo list in `name:url`, `name:url:ref`, or `name:url:ref@token` format. | Clones extra repositories beside the tests repo when the request omits `additional_repos`. Duplicate names are rejected. |

```bash
export AI_PROVIDER=claude
export AI_MODEL=sonnet
export AI_CLI_TIMEOUT=20
export PEER_AI_CONFIGS="gemini:gemini-2.5-pro,cursor:gpt-5.4-xhigh"
export PEER_ANALYSIS_MAX_ROUNDS=5
export ADDITIONAL_REPOS="product:https://github.com/acme/product:release-4.18,infra:https://github.com/acme/infra@ghp_example"
```

> **Note:** `PEER_AI_CONFIGS` and `ADDITIONAL_REPOS` are parsed when a request resolves them. Invalid values fail that analysis request instead of being ignored.

### Provider CLI Environment

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `ANTHROPIC_API_KEY` | string | unset | Authentication token for the Claude CLI. | Read by the external Claude CLI process. |
| `CLAUDE_CODE_USE_VERTEX` | string | unset | Vertex toggle for the Claude CLI. | Set to `1` to use Vertex-backed Claude auth. |
| `CLOUD_ML_REGION` | string | unset | Google Cloud region for Vertex-backed Claude auth. | Used only when the Claude CLI runs in Vertex mode. |
| `ANTHROPIC_VERTEX_PROJECT_ID` | string | unset | Google Cloud project ID for Vertex-backed Claude auth. | Used only when the Claude CLI runs in Vertex mode. |
| `GEMINI_API_KEY` | string | unset | Authentication token for the Gemini CLI. | Read by the external Gemini CLI process. |
| `CURSOR_API_KEY` | string | unset | Authentication token for the Cursor CLI. | Read by the external Cursor CLI process. |

```bash
export ANTHROPIC_API_KEY="replace-with-an-anthropic-key"
export GEMINI_API_KEY="replace-with-a-gemini-key"
export CURSOR_API_KEY="replace-with-a-cursor-key"
```

> **Note:** These variables are consumed by the provider CLIs that JJI launches. They are not modeled as FastAPI settings fields.

### Jira Integration

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `JIRA_URL` | URL | unset | Jira base URL. | Required for Jira enrichment and Jira helper APIs. |
| `JIRA_EMAIL` | string | unset | Jira Cloud email. | If set, Jira auth is treated as Cloud mode. |
| `JIRA_API_TOKEN` | string | unset | Jira Cloud API token. | Preferred credential in Cloud mode; fallback credential in Server/Data Center mode. |
| `JIRA_PAT` | string | unset | Jira personal access token. | Preferred credential in Server/Data Center mode; fallback credential in Cloud mode. |
| `JIRA_PROJECT_KEY` | string | unset | Default Jira project key. | Scopes Jira enrichment and default project selection. |
| `JIRA_SSL_VERIFY` | boolean | `true` | Jira TLS verification toggle. | `false` allows self-signed certificates. |
| `JIRA_MAX_RESULTS` | integer | `5` | Maximum Jira matches returned per search. | Caps enrichment search results. |
| `ENABLE_JIRA` | boolean | auto | Analysis-time Jira enrichment toggle. | `false` disables enrichment; unset enables it only when Jira URL, a usable credential, and a project key are present. |
| `ENABLE_JIRA_ISSUES` | boolean | `true` | Jira issue-creation feature toggle. | Independent of `ENABLE_JIRA`; `false` disables Jira issue creation. |

```bash
export JIRA_URL="https://jira.example.com"
export JIRA_EMAIL="alice@example.com"
export JIRA_API_TOKEN="replace-with-a-jira-token"
export JIRA_PROJECT_KEY="PROJ"
export JIRA_MAX_RESULTS=10
export ENABLE_JIRA=true
export ENABLE_JIRA_ISSUES=true
```

> **Note:** Auth selection is mode-aware: Cloud mode prefers `JIRA_API_TOKEN`; Server/Data Center mode prefers `JIRA_PAT`.

### GitHub Integration

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `GITHUB_TOKEN` | string | unset | Default GitHub token. | Used for GitHub-side lookups and as the server-side credential source for GitHub operations. |
| `ENABLE_GITHUB_ISSUES` | boolean | `true` | GitHub issue-creation feature toggle. | `false` disables GitHub issue creation; when enabled, the repo URL and token can still come from request-time inputs instead of server defaults. |

```bash
export GITHUB_TOKEN="replace-with-a-github-token"
export ENABLE_GITHUB_ISSUES=true
```

### Report Portal

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `REPORTPORTAL_URL` | URL | unset | Report Portal base URL. | Required to enable push integration. |
| `REPORTPORTAL_API_TOKEN` | string | unset | Report Portal API token. | Authenticates Report Portal API calls. |
| `REPORTPORTAL_PROJECT` | string | unset | Report Portal project name. | Target project used for launch matching and updates. |
| `REPORTPORTAL_VERIFY_SSL` | boolean | `true` | Report Portal TLS verification toggle. | `false` allows self-signed certificates. |
| `ENABLE_REPORTPORTAL` | boolean | auto | Report Portal feature toggle. | `false` disables the integration; unset enables it only when URL, token, and project are all present. |

```bash
export REPORTPORTAL_URL="https://reportportal.example.com"
export REPORTPORTAL_API_TOKEN="replace-with-an-rp-token"
export REPORTPORTAL_PROJECT="qe-gating"
export ENABLE_REPORTPORTAL=true
```

See [Push Classifications to Report Portal](push-classifications-to-report-portal.html) for usage details.

### Web Push and Alerting

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `VAPID_PUBLIC_KEY` | string | auto-generated | Public VAPID key for browser push subscriptions. | When both VAPID keys are unset, JJI generates and persists a key pair. |
| `VAPID_PRIVATE_KEY` | string | auto-generated | Private VAPID key for browser push subscriptions. | Must be provided together with `VAPID_PUBLIC_KEY` if you want fixed keys. |
| `VAPID_CLAIM_EMAIL` | string | `mailto:noreply@jji.local` | Contact email used in VAPID claims. | Used for both fixed and generated key pairs. |
| `SLACK_WEBHOOK_URL` | URL | unset | Slack incoming-webhook URL. | Enables Slack alert delivery; non-HTTPS values are flagged as configuration warnings. |
| `SMTP_HOST` | string | unset | SMTP host for email alerts. | Required for email alert delivery. |
| `SMTP_PORT` | integer | `587` | SMTP port for email alerts. | Port `587` enables `STARTTLS`. |
| `SMTP_USER` | string | unset | SMTP username. | Used for SMTP authentication when paired with `SMTP_PASSWORD`. |
| `SMTP_PASSWORD` | string | unset | SMTP password. | Used for SMTP authentication when paired with `SMTP_USER`. |
| `SMTP_FROM` | string | derived | Sender address for email alerts. | Defaults to `SMTP_USER`, or `jji@<SMTP_HOST>` when `SMTP_USER` is blank. |
| `ALERT_EMAIL_TO` | string | unset | Recipient address for email alerts. | Required together with `SMTP_HOST` to send email alerts. |

```bash
export VAPID_CLAIM_EMAIL="mailto:jji-admin@example.com"
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/example"
export SMTP_HOST="smtp.example.com"
export SMTP_PORT=587
export SMTP_USER="jji-alerts@example.com"
export SMTP_PASSWORD="replace-with-an-smtp-password"
export ALERT_EMAIL_TO="oncall@example.com"
```

> **Warning:** Set both `VAPID_PUBLIC_KEY` and `VAPID_PRIVATE_KEY` together if you want fixed keys. A partial pair is treated as invalid, and JJI falls back to generated keys.

### Metadata Rules File

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `METADATA_RULES_FILE` | path | unset | Path to a YAML or JSON rules file for automatic job metadata assignment. | Loaded once and cached for the process lifetime; file changes take effect after a server restart. |

```bash
export METADATA_RULES_FILE="/etc/jji/metadata-rules.yaml"
```

## `jji` Profile File: `config.toml`

`jji` looks for its profile file at ``$XDG_CONFIG_HOME/jji/config.toml`` or `~/.config/jji/config.toml` when `XDG_CONFIG_HOME` is unset.

See [CLI Command Reference](cli-command-reference.html) for command syntax.

### Top-Level Sections

| Section | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `[default]` | table | absent | Holds the default profile name. | Used when `--server` and `JJI_SERVER` are both absent. |
| `[defaults]` | table | absent | Shared profile values. | Merged into every `[servers.<name>]` entry before that server is used. |
| `[servers.<name>]` | table | absent | Named server profile. | Supplies a concrete server URL and client-side defaults. |

### `[default]` Fields

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `server` | string | unset | Name of the default profile. | Must be a non-empty trimmed string. |

```toml
[default]
server = "prod"
```

### `[defaults]` and `[servers.<name>]` Common Fields

> **Note:** `[defaults]` supports the same fields as `[servers.<name>]` except `server`. `url` can be defined in either place as long as each resolved profile ends up with a non-empty URL.

#### Connection and Auth

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `url` | string | unset | Base URL of the JJI server. | Required after defaults are merged. |
| `username` | string | `""` | Default username sent by `jji`. | Used for comments, reviews, and other user-attributed actions. |
| `no_verify_ssl` | boolean | `false` | Disable TLS verification for the CLI HTTP client. | Affects only the CLI connection to JJI, not Jenkins/Jira/Report Portal integration settings. |
| `api_key` | string | `""` | Default admin API key for the CLI. | Sent as the bearer token when a command needs admin access. |

#### Analysis Defaults

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `jenkins_url` | string | `""` | Default Jenkins base URL. | Sent only when the command does not pass `--jenkins-url`. |
| `jenkins_user` | string | `""` | Default Jenkins username. | Sent only when the command does not pass `--jenkins-user`. |
| `jenkins_password` | string | `""` | Default Jenkins password or API token. | Sent only when the command does not pass `--jenkins-password`. |
| `jenkins_ssl_verify` | boolean | unset | Default Jenkins TLS verification flag. | When omitted, the CLI leaves the server default unchanged. |
| `jenkins_timeout` | integer | `0` | Default Jenkins timeout override. | `0` means "do not send a value; let the server use its own default". |
| `tests_repo_url` | string | `""` | Default tests repository URL. | Supports the same `url:ref` format as the server environment variable. |
| `ai_provider` | string | `""` | Default AI provider. | Sent only when the command does not pass `--ai-provider`. |
| `ai_model` | string | `""` | Default AI model. | Sent only when the command does not pass `--ai-model`. |
| `ai_cli_timeout` | integer | `0` | Default AI CLI timeout override. | `0` means "do not send a value; let the server use its own default". |
| `peers` | string | `""` | Default peer config list. | Uses the same `provider:model,provider:model` format as `PEER_AI_CONFIGS`. |
| `peer_analysis_max_rounds` | integer | `0` | Default peer-round override. | `0` means "do not send a value"; non-zero values must be between `1` and `10` when used. |
| `additional_repos` | string | `""` | Default extra repo list. | Uses the same `name:url`, `name:url:ref`, or `name:url:ref@token` format as `ADDITIONAL_REPOS`. |
| `wait_for_completion` | boolean | unset | Default wait toggle for `jji analyze`. | When omitted, the CLI leaves the server default unchanged. |
| `poll_interval_minutes` | integer | `0` | Default poll-interval override. | `0` means "do not send a value". |
| `max_wait_minutes` | integer | `0` | Default max-wait override. | `0` means "do not send a value"; this is not the same as explicitly sending `0` in the API request body. |
| `force` | boolean | unset | Default force-analysis toggle. | When omitted, the CLI leaves the server default unchanged. |

#### Integration Defaults

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `jira_url` | string | `""` | Default Jira base URL. | Sent only when the command does not pass `--jira-url`. |
| `jira_email` | string | `""` | Default Jira Cloud email. | Sent only when the command does not pass `--jira-email`. |
| `jira_api_token` | string | `""` | Default Jira Cloud token. | Used for analysis requests that send Jira settings. |
| `jira_pat` | string | `""` | Default Jira personal access token. | Used for analysis requests that send Jira settings. |
| `jira_token` | string | `""` | Generic Jira token alias for CLI flows that accept a single Jira token field. | Used by Jira helper commands that do not split Cloud and Server/Data Center auth into separate options. |
| `jira_project_key` | string | `""` | Default Jira project key. | Used as the default project selection. |
| `jira_security_level` | string | `""` | Default Jira security level name for CLI Jira operations that accept it. | Used only by commands that support Jira security-level input. |
| `jira_ssl_verify` | boolean | unset | Default Jira TLS verification flag. | When omitted, the CLI leaves the server default unchanged. |
| `jira_max_results` | integer | `0` | Default Jira max-results override. | `0` means "do not send a value". |
| `enable_jira` | boolean | unset | Default Jira enrichment toggle. | When omitted, the CLI leaves the server default unchanged. |
| `github_token` | string | `""` | Default GitHub token. | Used for analysis-time GitHub lookups and GitHub CLI flows that need a token. |
| `github_repo_url` | string | `""` | Default GitHub repository URL for CLI GitHub operations that need a target repo. | Used only by commands that accept a GitHub repository URL. |

```toml
[default]
server = "prod"

[defaults]
username = "alice"
no_verify_ssl = false
jenkins_url = "https://jenkins.example.com"
ai_provider = "claude"
ai_model = "sonnet"
tests_repo_url = "https://github.com/acme/tests:main"
jira_url = "https://jira.example.com"
jira_project_key = "PROJ"

[servers.dev]
url = "http://localhost:8000"
no_verify_ssl = true
jenkins_ssl_verify = false

[servers.prod]
url = "https://jji.example.com"
api_key = "replace-with-an-admin-api-key"
github_token = "replace-with-a-github-token"
peers = "gemini:gemini-2.5-pro"
peer_analysis_max_rounds = 5
additional_repos = "product:https://github.com/acme/product:release-4.18"
```

### CLI Environment Variables and Global Flags

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `JJI_SERVER` / `--server` | string | unset | Server profile name or full JJI URL. | A full URL bypasses `config.toml` profile defaults for that connection. |
| `JJI_USERNAME` / `--user` | string | `""` | CLI username. | Overrides `config.toml` `username`. |
| `JJI_API_KEY` / `--api-key` | string | `""` | Admin bearer token. | Overrides `config.toml` `api_key`. |
| `JJI_NO_VERIFY_SSL` / `--no-verify-ssl` | boolean | unset | Disable TLS verification for the CLI HTTP client. | Overrides `config.toml` `no_verify_ssl`. |
| `--verify-ssl` | boolean | unset | Force TLS verification on. | Overrides `--no-verify-ssl`, `--insecure`, and `config.toml` `no_verify_ssl`. |
| `--insecure` | boolean | `false` | Alias for `--no-verify-ssl`. | Forces TLS verification off for the CLI HTTP client. |

```bash
export JJI_SERVER=prod
export JJI_USERNAME=alice
export JJI_API_KEY="replace-with-an-admin-api-key"

jji --verify-ssl health
jji --server https://jji.example.com --user release-bot results list
```

## Analysis Request Override Fields

This section documents the configuration-bearing request fields for `POST /analyze` and `POST /analyze-failures`.

See [REST API Reference](rest-api-reference.html) for required non-configuration fields such as `job_name`, `build_number`, `failures`, and `raw_xml`.

> **Note:** For `force`, `wait_for_completion`, `poll_interval_minutes`, `max_wait_minutes`, and `peer_analysis_max_rounds`, omitting the JSON key keeps the server value. Sending the key applies the request value, even if that value matches the schema default.

### Shared Override Fields

| Name | Type | Default When Omitted | Description | Effect |
| --- | --- | --- | --- | --- |
| `tests_repo_url` | string | server default | Tests repository URL. | Overrides `TESTS_REPO_URL` for one request; supports an optional `:ref` suffix. |
| `ai_provider` | `claude \| gemini \| cursor` | server default | AI provider for this request. | Overrides `AI_PROVIDER`; request fails if neither the request nor the server provides a provider. |
| `ai_model` | string | server default | AI model for this request. | Overrides `AI_MODEL`; request fails if neither the request nor the server provides a model. |
| `enable_jira` | boolean | server auto-detection | Analysis-time Jira enrichment toggle. | Overrides the server decision for this request only. |
| `ai_cli_timeout` | integer `> 0` | server default | AI CLI timeout in minutes. | Overrides `AI_CLI_TIMEOUT`. |
| `jira_url` | string | server default | Jira base URL. | Overrides `JIRA_URL`. |
| `jira_email` | string | server default | Jira Cloud email. | Overrides `JIRA_EMAIL`. |
| `jira_api_token` | string | server default | Jira Cloud API token. | Overrides `JIRA_API_TOKEN`. |
| `jira_pat` | string | server default | Jira personal access token. | Overrides `JIRA_PAT`. |
| `jira_project_key` | string | server default | Jira project key. | Overrides `JIRA_PROJECT_KEY`. |
| `jira_ssl_verify` | boolean | server default | Jira TLS verification flag. | Overrides `JIRA_SSL_VERIFY`. |
| `jira_max_results` | integer `> 0` | server default | Maximum Jira matches returned per search. | Overrides `JIRA_MAX_RESULTS`. |
| `raw_prompt` | string | none | Extra prompt text for this request. | Appended to the analysis prompt for this request only. |
| `github_token` | string | server default | GitHub token for this request. | Overrides `GITHUB_TOKEN` for GitHub-side lookups used during analysis. |
| `peer_ai_configs` | array of `AiConfigEntry` | server default | Peer review configuration list. | Overrides `PEER_AI_CONFIGS`; send `[]` to disable inherited peers. |
| `peer_analysis_max_rounds` | integer `1..10` | server default | Maximum debate rounds. | Overrides `PEER_ANALYSIS_MAX_ROUNDS` only when the key is explicitly present. |
| `additional_repos` | array of `AdditionalRepo` | server default | Extra repositories for analysis context. | Overrides `ADDITIONAL_REPOS`; send `[]` to disable inherited extra repos. |

#### `peer_ai_configs[]`

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `ai_provider` | `claude \| gemini \| cursor` | none | Peer provider name. | Selects the provider for that peer reviewer. |
| `ai_model` | string | none | Peer model identifier. | Must be non-blank after trimming. |

#### `additional_repos[]`

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `name` | string | none | Logical repo name. | Used as the cloned directory name; must be unique within the array and cannot contain path separators, `..`, or a leading `.`. |
| `url` | URL | none | Repository URL to clone. | Must be a valid absolute URL. |
| `ref` | string | `""` | Branch or tag name. | Empty means the remote default branch. |
| `token` | string or `null` | `null` | Token for cloning a private repo. | Used only for repository access; stored encrypted when request parameters are persisted. |

```json
{
  "job_name": "folder/job-name",
  "build_number": 1042,
  "ai_provider": "claude",
  "ai_model": "sonnet",
  "tests_repo_url": "https://github.com/acme/tests:main",
  "peer_ai_configs": [
    {
      "ai_provider": "gemini",
      "ai_model": "gemini-2.5-pro"
    }
  ],
  "peer_analysis_max_rounds": 5,
  "additional_repos": [
    {
      "name": "product",
      "url": "https://github.com/acme/product",
      "ref": "release-4.18"
    }
  ]
}
```

```json
{
  "raw_xml": "<testsuite>...</testsuite>",
  "ai_provider": "cursor",
  "ai_model": "gpt-5.4-xhigh",
  "peer_ai_configs": [],
  "additional_repos": []
}
```

### `POST /analyze`-Only Override Fields

| Name | Type | Default When Omitted | Description | Effect |
| --- | --- | --- | --- | --- |
| `jenkins_url` | string | server default | Jenkins base URL. | Overrides `JENKINS_URL`. |
| `jenkins_user` | string | server default | Jenkins username. | Overrides `JENKINS_USER`. |
| `jenkins_password` | string | server default | Jenkins password or API token. | Overrides `JENKINS_PASSWORD`. |
| `jenkins_ssl_verify` | boolean | server default | Jenkins TLS verification flag. | Overrides `JENKINS_SSL_VERIFY`. |
| `jenkins_timeout` | integer `> 0` | server default | Jenkins API timeout in seconds. | Overrides `JENKINS_TIMEOUT`. |
| `jenkins_artifacts_max_size_mb` | integer `> 0` | server default | Artifact size cap for this request. | Overrides `JENKINS_ARTIFACTS_MAX_SIZE_MB`. |
| `get_job_artifacts` | boolean | server default | Artifact-download toggle for this request. | Overrides `GET_JOB_ARTIFACTS`. |
| `force` | boolean | server default | Force-analysis toggle. | Overrides `FORCE_ANALYSIS` only when the key is explicitly present. |
| `wait_for_completion` | boolean | server default | Wait toggle for this request. | Overrides `WAIT_FOR_COMPLETION` only when the key is explicitly present. |
| `poll_interval_minutes` | integer `> 0` | server default | Poll interval while waiting. | Overrides `POLL_INTERVAL_MINUTES` only when the key is explicitly present. |
| `max_wait_minutes` | integer `>= 0` | server default | Maximum wait duration. | Overrides `MAX_WAIT_MINUTES` only when the key is explicitly present; explicit `0` means no wait limit. |

```json
{
  "job_name": "folder/job-name",
  "build_number": 1042,
  "jenkins_url": "https://jenkins.example.com",
  "jenkins_user": "ci-bot",
  "jenkins_password": "replace-with-a-token",
  "wait_for_completion": false,
  "force": true,
  "get_job_artifacts": false
}
```

## Metadata Rules File Format

The file referenced by `METADATA_RULES_FILE` can be either:

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| Top-level `metadata_rules` key | array of rule objects | none | Standard wrapper form for YAML or JSON. | JJI loads the array under `metadata_rules`. |
| Bare top-level array | array of rule objects | none | Shorthand form for YAML or JSON. | JJI treats the entire file as the rules list. |

### Rule Object Fields

| Name | Type | Default | Description | Effect |
| --- | --- | --- | --- | --- |
| `pattern` | string | none | Match expression for the Jenkins job name. | Required. Treated as a glob unless it contains a named regex capture group such as `(?P<version>...)`. |
| `team` | string | unset | Team value to assign. | First matching rule wins for this field. |
| `tier` | string | unset | Tier value to assign. | First matching rule wins for this field. |
| `version` | string | unset | Version value to assign. | First matching rule wins for this field. |
| `labels` | string or array of strings | unset | Labels to assign. | Labels accumulate across all matching rules and duplicates are removed. |

### Matching Rules

| Rule | Effect |
| --- | --- |
| Glob matching | Used for patterns that do not contain `(?P<...>)`. |
| Regex matching | Used only when the pattern contains at least one named capture group. |
| Named capture groups | Captured values become metadata fields with the same names. |
| Explicit field precedence | Explicit `team`, `tier`, or `version` values override regex-captured values from the same rule. |
| Scalar merge strategy | `team`, `tier`, and `version` use first-match-wins. |
| Label merge strategy | `labels` accumulate from every matching rule. |
| Reload behavior | Rules are cached for the process lifetime; restart the server to pick up file changes. |

> **Warning:** A pattern like `^job-.*$` is still treated as a glob. To force regex mode, include at least one named capture group such as `(?P<version>...)`.

```yaml
metadata_rules:
  - pattern: "release-*"
    labels: ["release"]

  - pattern: "console-t1-*"
    team: "console"
    tier: "t1"

  - pattern: "^console-(?P<version>\\d+\\.\\d+)-(?P<tier>t[12])$"
    team: "console"
    labels: ["versioned"]
```

See [REST API Reference](rest-api-reference.html) for metadata preview endpoints and [CLI Command Reference](cli-command-reference.html) for metadata-related CLI commands.

## Related Pages

- [Copy Common Deployment Recipes](copy-common-deployment-recipes.html)
- [Customize AI Analysis](customize-ai-analysis.html)
- [Manage Users, Access, and Token Usage](manage-users-access-and-token-usage.html)
- [CLI Command Reference](cli-command-reference.html)
- [REST API Reference](rest-api-reference.html)

---
