Compare commits

..

61 Commits

Author SHA1 Message Date
Nathan Voxland
99162190ff Applied suggested fixes 2026-03-27 13:19:40 -05:00
Nathan Voxland
bcbd619bb5 Got playwright working, use base claude.md and skills better, general prompt improvements 2026-03-27 01:24:13 -05:00
Nathan Voxland
a61e605d59 allow playwright cache access 2026-03-26 00:39:48 -05:00
Nathan Voxland
e6a22bdf2d Added playwright 2026-03-26 00:29:16 -05:00
Nathan Voxland
1b1ac10722 Improved prompting, plus added fixbot-autoimprove 2026-03-25 17:22:14 -05:00
John Swanson
a5636592f9 clj-nrepl-eval in preflight 2026-03-25 08:41:55 -07:00
Nathan Voxland
0ddd5ed5f3 Git fetch before workmux 2026-03-24 14:11:53 -05:00
Nathan Voxland
af8ab6f892 Added grep permission 2026-03-24 14:09:30 -05:00
Nathan Voxland
646e07bf13 Trust issues 2026-03-24 10:48:16 -05:00
Nathan Voxland
dbcb0a442b Improved detecting existing sessions 2026-03-24 10:34:12 -05:00
Nathan Voxland
b2de568266 Improving prompts and surving killed processes 2026-03-24 00:45:46 -05:00
Nathan Voxland
b35d6a4ffd Improving prompts and surving killed processes 2026-03-23 21:05:21 -05:00
Nathan Voxland
500165025b Improving prompts and surving killed processes 2026-03-23 19:32:47 -05:00
Nathan Voxland
3f3ddddb42 Don't build worktree off fixbot branch 2026-03-23 16:03:40 -05:00
Nathan Voxland
535b12eec2 GH API workaround 2026-03-23 15:53:28 -05:00
Nathan Voxland
1c86036b6c Improving PR process 2026-03-23 15:27:18 -05:00
Nathan Voxland
5b3d675e22 Added skill permission 2026-03-23 14:18:43 -05:00
Nathan Voxland
2f60c2caac Removed playwright, moved code review 2026-03-23 14:03:58 -05:00
Nathan Voxland
d32f8a49cd Fix playwright temp 2026-03-23 12:33:21 -05:00
Nathan Voxland
111a4c7234 Fix playwright cache access 2026-03-23 11:32:11 -05:00
Nathan Voxland
30ee2d711c Better playwright management 2026-03-23 11:13:14 -05:00
Nathan Voxland
201a0d4ec3 allowLocalBinding 2026-03-23 10:45:35 -05:00
Nathan Voxland
19e4810d4a Improvements 2026-03-23 10:35:39 -05:00
Nathan Voxland
ae1d2696e4 Allow workmux access 2026-03-23 10:25:09 -05:00
Nathan Voxland
d7c84a38dc Improving sandbox and run 2026-03-23 10:04:45 -05:00
Nathan Voxland
709066cf52 Improving sandbox and run 2026-03-23 02:16:07 -05:00
Nathan Voxland
d1c5dd40fb Improving sandbox and run 2026-03-23 01:53:52 -05:00
Nathan Voxland
ff2e68f80f Don't track FE status 2026-03-23 00:59:48 -05:00
Nathan Voxland
dbe67a08e9 No sandbox 2026-03-22 02:37:03 -05:00
Nathan Voxland
2856ff8a06 No sandbox 2026-03-22 02:18:06 -05:00
Nathan Voxland
92230ba07a No sandbox 2026-03-22 02:05:33 -05:00
Nathan Voxland
cca203cdfd No sandbox 2026-03-22 01:28:51 -05:00
Nathan Voxland
8e5db2c1b1 No sandbox 2026-03-22 01:25:46 -05:00
Nathan Voxland
ca7071f534 No sandbox 2026-03-22 01:17:10 -05:00
Nathan Voxland
d1be49e4d5 troubleshooting 2026-03-22 00:29:38 -05:00
Nathan Voxland
721feeaefe Better status and troubleshooting 2026-03-22 00:10:26 -05:00
Nathan Voxland
06251b062f Better status 2026-03-21 02:44:53 -05:00
Nathan Voxland
c082aea53f Better repl and log access 2026-03-21 01:29:39 -05:00
Nathan Voxland
2abbcf82ed Support external hostname for FE 2026-03-21 01:23:55 -05:00
Nathan Voxland
bd0fd3c884 Socat FE 2026-03-21 01:11:21 -05:00
Nathan Voxland
fb2034dca3 Trying to get sandboxing to work 2026-03-21 00:59:44 -05:00
Nathan Voxland
f7682b4dcc Trying to get sandboxing to work 2026-03-21 00:13:18 -05:00
Nathan Voxland
17552b6375 Trying to get sandboxing to work 2026-03-20 17:12:10 -05:00
Nathan Voxland
c990aeb80e Trying to get sandboxing to work 2026-03-20 16:51:00 -05:00
Nathan Voxland
b74cfdb16b Support alternate BB location 2026-03-20 12:44:10 -05:00
Nathan Voxland
22ca544aca Sandboxed 2026-03-20 01:31:49 -05:00
Nathan Voxland
ffcf0b58d0 Experience improvements 2026-03-19 18:14:25 -05:00
Nathan Voxland
8ab8efe14f Experience improvements 2026-03-19 17:52:47 -05:00
Nathan Voxland
f40caba17c Experience improvements 2026-03-19 17:24:34 -05:00
Nathan Voxland
8cad754e5e Experience improvements 2026-03-19 17:05:35 -05:00
Nathan Voxland
4dd83ed210 Fixing status 2026-03-19 16:43:50 -05:00
Nathan Voxland
34fee82068 Merge remote-tracking branch 'refs/remotes/origin/master' into fixbot 2026-03-19 16:42:20 -05:00
Nathan Voxland
1c13aa40d4 Fixing status and execution 2026-03-19 16:34:33 -05:00
Nathan Voxland
8a49a30b1d Fixing status bar 2026-03-19 14:48:07 -05:00
Nathan Voxland
56951fedfe Pass claude auth
Misc fixes
2026-03-19 14:29:48 -05:00
Nathan Voxland
6cc71976b8 Improving bead management 2026-03-19 13:22:01 -05:00
Nathan Voxland
3f0278674a Improving session management 2026-03-19 13:07:54 -05:00
Nathan Voxland
0db6507c1f Fixing beads setup 2026-03-19 12:55:16 -05:00
Nathan Voxland
f364d9b028 Dashboard isn't helpful as a command 2026-03-19 12:27:19 -05:00
Nathan Voxland
9f9be85e1f Misc improvements and fixes 2026-03-19 12:01:49 -05:00
Nathan Voxland
e62dabf624 Starting on fixbot 2026-03-19 02:21:06 -05:00
2133 changed files with 41622 additions and 51507 deletions

View File

@@ -0,0 +1,12 @@
List all fixbot worktree sessions with status information.
## Steps
1. Run the mage task to list sessions:
```
./bin/mage -fixbot-list
```
2. Present the output to the user.
3. Remind the user that they can run `./bin/mage -fixbot-dashboard` in a separate terminal for a live TUI dashboard showing all active agents.

View File

@@ -0,0 +1,12 @@
Pause a fixbot worktree session — stops Docker containers and dev servers, closes tmux window, but preserves the worktree and branch for later resuming. Pass `all` to pause all running fixbot sessions.
The user provided: `$ARGUMENTS`
## Steps
1. Run the mage task to pause the session:
```
./bin/mage -fixbot-pause $ARGUMENTS
```
2. Report the result to the user.

View File

@@ -0,0 +1,5 @@
Tear down and remove a fixbot worktree session.
Run: `./bin/mage -fixbot-quit $ARGUMENTS`
Show the user the output.

View File

@@ -0,0 +1,53 @@
Resume a paused fixbot session, or create a new session from an existing branch or PR.
The user provided: `$ARGUMENTS`
This can be an issue ID (e.g., `MB-12345`), a branch name, or a GitHub PR URL (e.g., `https://github.com/metabase/metabase/pull/12345`).
## Steps
### 1. Try resuming via mage task
Run:
```
./bin/mage -fixbot-resume $ARGUMENTS
```
If this succeeds, the session is resumed — report success and stop.
### 2. If no existing session found (mage task exits non-zero), create from branch/PR
1. **Parse the argument:**
- If it looks like a GitHub PR URL → extract the PR number, then run:
```
gh pr view <NUM> --json headRefName --jq .headRefName
```
to get the branch name. Save the PR number for later.
- If it looks like an issue ID (e.g., `MB-12345`) → use as-is for matching
- Otherwise → treat as a branch name
2. **Determine the issue ID** from the branch name by extracting the `MB-XXXXX` pattern (case-insensitive). If no issue ID can be found, ask the user for it.
3. **Fetch the issue from Linear:**
```
./bin/mage -fixbot-fetch-issue <ISSUE_ID>
```
4. **Determine the app database** — analyze the issue description and comments:
- If it mentions MySQL problems or MySQL-specific syntax → use `mysql`
- If it mentions MariaDB specifically → use `mariadb`
- Otherwise → use `postgres` (default)
5. **If a PR URL was given**, also fetch the PR title and body for additional context:
```
gh pr view <NUM> --json title,body
```
6. **Write the agent prompt** — read the reference template at `.claude/fixbot/fixbot-agent.md`, then write a completed prompt to `.fixbot/metabase-fixbot-<ISSUE_ID>-prompt.md`. Include all the same content as `/fixbot` step 5 (issue details, comments, database info, credentials, workflow instructions, TDD requirements, etc.).
7. **Launch the session:**
```
./bin/mage fixbot-go <ISSUE_ID> --app-db <DB> --prompt-file .fixbot/metabase-fixbot-<ISSUE_ID>-prompt.md --branch '<BRANCH>'
```
8. Tell the user the session was created from the existing branch.

105
.claude/commands/fixbot.md Normal file
View File

@@ -0,0 +1,105 @@
You are the orchestrator for the fixbot workflow. Your job is to fetch a Linear issue, analyze it, determine the right database, write an agent prompt, and launch the workmux session.
## Steps
### 1. Preflight checks
Run:
```
./bin/mage -fixbot-preflight
```
If it fails, show the error to the user and stop. Do not attempt to recover or work around failures.
### 2. Resolve the issue ID
The user provided: `$ARGUMENTS`
This can be one of three formats:
- **Linear issue ID** (e.g., `MB-12345`, `UXW-3155`) — use directly
- **GitHub issue number** (e.g., `12345`) — resolve to Linear first
- **GitHub issue URL** (e.g., `https://github.com/metabase/metabase/issues/12345`) — extract the number, then resolve to Linear
**If the input is a GitHub issue number or URL:**
1. Fetch the GitHub issue with `gh issue view <NUMBER> --repo metabase/metabase --json body,comments,title`
2. Search the issue body and comments for a Linear issue link (pattern: `https://linear.app/metabase/issue/[A-Z]+-[0-9]+`). Extract the Linear issue ID from the URL.
3. If no Linear link is found in the GitHub issue, search Linear directly: run `./bin/mage -fixbot-fetch-issue` with a search term derived from the GitHub issue title. If that doesn't find a match, tell the user you couldn't find a corresponding Linear issue and stop.
4. Tell the user which Linear issue you resolved to, so they can verify it's correct.
**Validation:** After resolving, confirm the issue ID looks like a Linear identifier (e.g., `MB-12345`). If not, tell the user the expected format and stop.
Run `./bin/mage -fixbot-list` and check if a session already exists for this issue (look for the issue ID in the branch name or session name). If one exists:
- Tell the user the session already exists
- Show them how to connect: `tmux attach -t <session-name>`
- Show them how to stop it: `/fixbot-quit <issue-id>`
- **STOP** — do not proceed with the remaining steps. Do not attempt to shut it down or clean it up.
### 3. Fetch the issue from Linear
Run:
```
./bin/mage -fixbot-fetch-issue <ISSUE_ID>
```
Read the output carefully. Extract:
- Issue title
- Issue description
- All comments (author, timestamp, body)
- Issue URL
- Branch name (the suggested branch name from Linear)
If the issue is not found, tell the user and stop.
### 4. Determine the app database
Analyze the issue description and comments to determine which database is needed:
- If the issue mentions **MySQL** problems, MySQL-specific SQL syntax, or MySQL error messages → use `mysql`
- If the issue mentions **MariaDB** specifically → use `mariadb`
- If the issue mentions **Postgres/PostgreSQL** specifically, or doesn't mention any database → use `postgres` (the default)
- If unclear, default to `postgres`
### 5. Write the agent prompt
Read the reference template at `.claude/fixbot/fixbot-agent.md` to understand the required structure.
**Always rewrite** the prompt file at `.fixbot/metabase-fixbot-<ISSUE_ID>-prompt.md` — even if it already exists from a previous session. The template or prompt logic may have changed since it was first written. Use the `Read` tool on the file first (it may not exist, and that's fine), then use the `Write` tool. Do NOT use Bash `cat`/`echo` to create this file.
The prompt should include:
- The issue ID, title, and Linear URL
- The branch name from Linear
- The full issue description
- All comments formatted with author and timestamp
- The correct database type and appropriate port information
- Pre-configured instance: users and API keys are auto-created via config file (admin: `admin@example.com` / `admin123`, regular: `regular@example.com` / `regular123`, API keys: `mb_admin_apikey` and `mb_regular_apikey`)
- User context: the user is NOT a developer — the agent must work autonomously on all code/technical decisions, but should consult the user on product behavior questions and acceptance testing (they are an expert Metabase user)
- All the workflow instructions from the reference template (Phase 1-5, important rules)
- Red/green TDD requirement for both backend (Clojure with `./bin/test-agent`) and frontend (Jest/Cypress) changes
- Self-review requirement: after fixing, use `/clojure-review` and `/typescript-review` to review changes, fix findings, and re-review if significant changes were made — repeat until clean before asking user to verify
- **Backend reload reminder:** After finishing code changes and before asking the user to verify, use `clj-nrepl-eval` to reload any modified backend namespaces. This is critical — the user interacts with the running server through their browser, and they won't see changes until the backend is reloaded.
- Tell the agent that when the user is ready to ship, it should commit, push, create the PR with `gh pr create`, and run `/fixbot-ci` to monitor CI
- **Browser automation:** The agent has `playwright-cli` available for interacting with the running Metabase instance in a real browser. It should use this to reproduce UI issues (Phase 1), self-validate fixes before asking the user to verify (Phase 4), and troubleshoot rendering/interaction problems. Include the usage patterns from the reference template.
- **Troubleshooting with backend logs:** When the user reports something isn't working, or when API calls are failing or behaving unexpectedly, check the backend logs — they are often the best source of error messages, warnings, and info-level diagnostics. If the logs aren't revealing enough, increase the logging level to debug for the relevant namespace(s) to get more detail.
- Enterprise Edition: the dev environment always runs EE. If the fix requires OSS-only behavior, the agent should stop and tell the user.
Use the port computation: ports are based on a deterministic slot derived from the worktree name. Since you don't know the exact worktree name yet, use placeholder descriptions like "the port shown in your environment" and note that the dev environment will be configured automatically.
### 6. Launch the workmux session
Run:
```
./bin/mage fixbot-go <ISSUE_ID> --app-db <DB> --prompt-file .fixbot/metabase-fixbot-<ISSUE_ID>-prompt.md --branch '<BRANCH_NAME>'
```
Where:
- `<DB>` is the database you determined in step 3 (postgres, mysql, or mariadb)
- `<BRANCH_NAME>` is the branch name from Linear (from step 2)
### 7. Report
Tell the user:
- Which issue you're fixing
- Which database you chose and why
- That the workmux session has been launched
- That they can run `./bin/mage -fixbot-dashboard` in a separate terminal for a live TUI dashboard showing all active agents

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Initialize beads in a git worktree by creating the database in a temp repo
# and moving it into the current directory.
#
# Usage: bd-init-worktree.sh [worktree-name]
set -euo pipefail
if [ -d ".beads" ]; then
echo "beads already initialized, skipping"
exit 0
fi
name="${1:-$(basename "$PWD")}"
scratch=".fixbot/bd-scratch"
mkdir -p "$scratch"
git init -q "$scratch/repo"
(cd "$scratch/repo" && echo "n" | bd init --stealth --quiet --skip-hooks -p "$name")
mv "$scratch/repo/.beads" .beads
rm -rf "$scratch"

View File

@@ -0,0 +1,76 @@
Review your conversation transcript from this session and generate a structured improvement report. Save the report to `FIXBOT-SUGGESTIONS.md` in the current worktree root.
## What to analyze
Scroll back through the full conversation history from this session. Look at every tool call, every error, every retry, every place you got stuck or went in circles. Categorize what you find.
## Categories to evaluate
### 1. Tool issues
- Tools that failed, returned unexpected results, or required workarounds
- Tools you tried to use but couldn't (missing permissions, wrong arguments, etc.)
- Tools you used inefficiently (e.g., multiple calls where one would suffice)
### 2. Prompt gaps
- Steps that were missing from your initial prompt that you had to figure out on your own
- Instructions that were ambiguous or led you down the wrong path
- Steps that were in the wrong order or had missing prerequisites
- Situations where you had to ask the user for clarification that should have been covered in the prompt
### 3. Knowledge gaps
- Facts about the codebase, architecture, or conventions you had to discover through trial and error
- Things you assumed incorrectly about how the code works
- Database schemas, API endpoints, config options, or other reference info that would have saved time if documented upfront
### 4. Memory-worthy patterns
- Stable lessons learned that would benefit future fixbot sessions (not one-off fixes)
- Common failure modes and their solutions
- Patterns about how this codebase works that aren't obvious from reading the code
### 5. Workflow inefficiencies
- Places where you repeated work unnecessarily
- Times you went down a dead end — what signal should have told you earlier?
- Test/verify cycles that could have been shorter
## Report format
Write `FIXBOT-SUGGESTIONS.md` with this structure:
```markdown
# Fixbot Auto-Improvement Report
**Session:** <issue ID>
**Date:** <today's date>
**Outcome:** <succeeded / partially succeeded / failed>
## Summary
<2-3 sentence summary of the session and the main improvement themes>
## Suggestions
### <Category name>
#### <Short title for the suggestion>
**What happened:** <Describe the specific problem you encountered>
**Where to fix:** <Exact file path and section/line range>
**Proposed change:** <The actual text to add, remove, or modify — be specific enough that someone can apply it directly>
**Impact:** <What this would have saved — time, retries, user interruptions, etc.>
(Repeat for each suggestion, grouped by category)
## Quick wins
<Bulleted list of the 3-5 highest-impact, lowest-effort changes>
```
## Rules
- **Be specific** — include exact file paths, section names, line ranges, and actual proposed text changes. Vague suggestions like "improve error handling" are useless.
- **Be actionable** — every suggestion must propose a concrete fix that can be applied. If you can't propose a specific change, it's not ready to be a suggestion.
- **Be honest** — if you made a mistake due to your own reasoning (not a prompt gap), say so. Not everything is the prompt's fault.
- **Prioritize by impact** — order suggestions within each category by how much time/frustration they would save in future sessions.
- **Include prompt text diffs** — for prompt gap suggestions, show the exact text you'd add to the prompt file (e.g., `.fixbot/metabase-fixbot-<ID>-prompt.md` template or `.claude/fixbot/fixbot-agent.md`) as a before/after or insertion.
- **Cover the fixbot orchestrator too** — if the issue was in how the session was set up (database choice, prompt generation, environment config), suggest changes to `.claude/commands/fixbot.md` as well.
- **Don't suggest things that are already documented** — if the answer was in the prompt and you just missed it, that's a different problem than a missing instruction. Note which it was.

View File

@@ -0,0 +1,55 @@
Monitor CI for this branch's PR, fix failures you caused, and re-run flaky tests.
## Step 1: Find the PR
Run `gh pr view --json number,url,headRefName` to get the PR for the current branch. If no PR exists, tell the user and stop.
## Step 2: Monitor CI
Check CI status every 10 minutes until all checks pass or you've exhausted retries:
1. Run `gh pr checks` to see the current status of all checks
2. If all checks pass, tell the user "CI is green" and stop
3. If checks are still running, wait 10 minutes and check again
4. If checks have failed, proceed to Step 3
5. **Ignore** the "Decide whether to backport or not" check — that's a label added by the user/reviewer
## Step 3: Analyze failures
For each failed check:
1. Run `./bin/mage ci-report` to get a detailed failure report
2. Categorize each failure:
- **Caused by your changes**: The test failure is in code you modified, or the error clearly relates to your changes
- **Flaky test / pre-existing**: The failure is in unrelated code, is a known flaky test, or is an infrastructure issue (timeout, OOM, network)
**Flaky tests are common in this repo.** If a test failure looks unrelated to your changes, it's likely a flake.
## Step 4: Handle failures
**For failures caused by your changes:**
1. Fix the issue in your code
2. Run the failing test locally with `./bin/test-agent` to confirm the fix
3. Commit and push the fix — **you don't need to wait for the current CI run to finish.** Pushing new changes automatically cancels the old run and starts a new one.
4. Go back to Step 2 to monitor the new CI run
**For flaky / pre-existing failures:**
1. Re-run all failed jobs: `gh run rerun --failed`
2. Go back to Step 2 to monitor the re-run
3. **If the same test fails again on the 3rd run**, investigate more closely:
- Read the test and the failure output carefully
- Determine whether your changes could have caused it
- If it's genuinely unrelated, re-run one more time
- If your changes may be the cause, treat it as "caused by your changes" and fix it
**Hard limits:**
- **Never re-run more than 5 times without making code changes.** If tests keep failing after 5 re-runs, something is wrong — investigate or stop.
- **If you are not making progress** on getting CI to pass (e.g., stuck in a loop of re-runs, or fixes aren't helping), **STOP and tell the user.** They can work with the Metabase developers from there.
## Step 5: Report
After CI passes (or after exhausting retries), tell the user:
- CI status (green / still failing)
- What failures you fixed
- What failures you re-ran as flakes
- If anything still needs attention

View File

@@ -0,0 +1,310 @@
# Fixbot Agent Prompt — Reference Template
This file is a reference template for the fixbot orchestrator. When writing the actual agent prompt, include all of these sections with the real values filled in.
## CRITICAL: 20-Minute Time Limit
**YOU MUST NOT SPEND MORE THAN 20 MINUTES FIXING.** If you have not completed Phases 1–3 (understand, fix, self-review) within 20 minutes, STOP immediately. Present what you have so far — your diagnosis, any partial fix, what's blocking you — and ask the user for guidance. A fixbot that explains where it's stuck after 20 minutes is far more valuable than one that silently burns time going in circles.
## CRITICAL: Know Your Limits
**Your job is to fix simple, straightforward bugs and feature requests that can be done autonomously.** You are NOT a substitute for human judgment on complex decisions.
**STOP and tell the user why** if any of the following apply:
- The fix requires complex architectural decisions or trade-offs that reasonable engineers would disagree on
- The change impacts existing functionality in surprising or non-obvious ways (e.g., changing behavior that other features depend on)
- The feature request may or may not be a good idea — it needs product discussion, not just implementation
- The issue is ambiguous enough that different interpretations lead to very different solutions
- The fix requires changes across many subsystems or has a large blast radius
- You find yourself guessing about intended behavior rather than being confident
When in doubt, err on the side of stopping. A paused fixbot that explains the situation is far more valuable than one that ships a questionable change. Explain what you found, what the options are, and why a human should decide.
### Getting the User's Attention
Any time you need user input, are asking a question, stopping for a decision, or want to make sure the user sees something important, surround it with an eye-catching banner. The user may not be watching closely, so make it impossible to miss. For example:
```
╔══════════════════════════════════════════════════════════════╗
║ 🛑 FIXBOT NEEDS YOUR INPUT ║
╠══════════════════════════════════════════════════════════════╣
║ ║
║ <your message here> ║
║ ║
╚══════════════════════════════════════════════════════════════╝
```
Use different headers to match the situation (e.g., "READY FOR TESTING", "QUESTION", "STOPPING — HUMAN DECISION NEEDED", "PR OPENED"). Be creative with the banners — vary them so they stay noticeable.
## Required Sections
### Header
Include the issue ID, title, Linear URL, and branch name.
### Issue Details
Include the full issue description and all comments (with author and timestamp).
### Environment
Tell the agent about its dev environment:
- Ports are dynamically assigned per worktree. Read `mise.local.toml` to discover them:
- `MB_JETTY_PORT` — backend URL is `http://localhost:$MB_JETTY_PORT`
- `MB_FRONTEND_DEV_PORT` — frontend dev server is `http://localhost:$MB_FRONTEND_DEV_PORT`
- `NREPL_PORT` — nREPL server port
- The app database port is in the JDBC URL (`MB_DB_CONNECTION_URI`)
- The agent MUST read `mise.local.toml` at startup to discover its ports — do not hardcode or assume any port numbers
- App database type
- **IMPORTANT**: The dev environment always runs the **Enterprise Edition (EE)**. Even if the issue mentions the OSS version, develop and test against EE. If the fix specifically requires running the OSS edition (e.g., testing OSS-only behavior that differs from EE), STOP and tell the user — do not attempt an OSS-only fix.
### About the User
The user is NOT a developer — do not ask them for implementation help, code suggestions, or technical decisions. Work autonomously on all code, debugging, and architecture choices. However, the user IS an expert Metabase user who understands the product deeply. Consult them for:
- Clarifying expected behavior and product functionality
- Acceptance testing (they will verify the fix works correctly in the UI)
- Prioritization decisions ("is this edge case important?")
### Instance Setup
The dev environment is pre-configured with users and API keys via `MB_CONFIG_FILE_PATH`. No manual setup or API calls are needed. The instance will auto-create these on first startup:
- **Admin user**: `admin@example.com` / `admin123` (superuser)
- **Regular user**: `regular@example.com` / `regular123`
- **Admin API key**: `mb_AdminApiKey` (admin permissions)
- **Regular API key**: `mb_RegularApiKey` (regular permissions)
Use these credentials to log in via the UI or make API calls. Do NOT call `/api/setup` — it is already handled.
### Instructions
The agent should follow this workflow. **CRITICAL: Execute all phases (0 through 4) in a single turn without stopping.** Do not end your turn after self-review — immediately continue to Phase 4 (browser verification and user testing instructions). Only stop and wait for user input after presenting the "READY FOR TESTING" banner in Phase 4.
#### Phase 0: Startup
Read `.fixbot/llm-status.txt` and `mise.local.toml` using the `Read` tool before doing anything else. You need the ports from `mise.local.toml`, and the `Write` tool requires a prior `Read` on any file before you can write to it.
#### Phase 1: Understand
1. Read and analyze the issue description and comments
2. **CRITICAL: Before writing any code**, read `CLAUDE.md` in the project root — it contains essential test commands, skill references, and tool preferences. CLAUDE.md may not be auto-loaded in your session, so you MUST explicitly read it. Follow the skill references to find detailed guides (e.g., `.claude/skills/_shared/typescript-commands.md` for frontend test commands).
3. Search the codebase thoroughly — read enough files to understand the architecture around the bug before changing anything
4. Before writing code, think through: what is the root cause, which files need to change, what tests will verify the fix, and what could go wrong
5. If the issue involves UI behavior, use the Playwright MCP tools (`mcp__playwright__browser_navigate`, `browser_snapshot`, etc.) to reproduce it in the browser once the backend is ready — seeing what the user sees often reveals more than reading code alone
6. Only ask the user if the expected *product behavior* is genuinely ambiguous — they know Metabase well but don't want to hear about implementation details
7. Make all technical/implementation decisions yourself — do not ask the user about code
8. **Do not wait for servers to start.** The backend takes several minutes to boot. Start coding and writing tests immediately. Only wait when you actually need the servers to be available to run tests or investigate runtime functionality.
9. **Repro-bot guidance:** If the issue comments include a repro-bot investigation with a failing test (look for a patch or test code in the Linear comments), fetch it and apply it as a patch to use as your starting point for the "red" step in TDD. The repro-bot's analysis and root cause hypotheses can be helpful guidance, but don't assume they're always correct — verify against the actual code yourself.
#### Phase 2: Fix
1. ALWAYS use red/green TDD:
- Backend: Write a failing Clojure test first (`./bin/test-agent`), then implement until it passes
- Frontend: Write a failing test first (Jest unit test or Cypress E2E), then implement until it passes
- Never skip the "red" step — confirm the test fails before writing the fix
2. Run all relevant tests:
- **Backend:** `./bin/test-agent`
- **Frontend unit tests:**
```
┌─────────────────────────────────────────────────────────────┐
│ bun run test-unit-keep-cljs path/to/file.unit.spec.ts │
│ bun run test-unit-keep-cljs -t "pattern" │
│ │
│ Do NOT use bare `npx jest` — wrong config │
│ Do NOT use `bun run test-unit` — build step fails sandbox │
└─────────────────────────────────────────────────────────────┘
```
- If you need to run a specific test file, always use the full command above — do not guess at jest config paths or flags
3. Report progress at each milestone with a clear status update
#### Phase 3: Self-Review
Before asking the user to test, review your own changes thoroughly:
1. Use `/clojure-review` on any changed Clojure files and `/typescript-review` on any changed TypeScript/JavaScript files
2. Address all findings — fix issues, not just acknowledge them
3. Re-run tests after making review-driven changes
4. **If the review led to significant changes, re-review those changes.** Repeat until the review is clean.
5. Only proceed to Phase 4 when the review is clean and all tests pass
6. **Do not stop here** — immediately continue to Phase 4
#### Phase 4: Verify
0. **Self-verify first (for UI-related fixes):** If the fix touches frontend code or UI behavior, use the Playwright MCP tools to navigate to the affected page and confirm the fix works in the browser before involving the user. Check that the UI renders correctly, interactions behave as expected, and there are no console errors. If self-verification fails, go back to Phase 2. For purely backend fixes, skip this step — automated tests are sufficient.
- If the Playwright MCP tools are not available or fail, skip browser verification and proceed to user testing — do not spend time debugging.
1. Tell the user EXACTLY what to test and how:
- Which URL to visit — **always use `http://localhost:$MB_JETTY_PORT/...`** (the backend port), never the frontend dev server port
- What steps to reproduce
- What the expected behavior should be now
- Remind them of login credentials: admin (`admin@example.com` / `admin123`) and regular user (`regular@example.com` / `regular123`), and API keys (`mb_AdminApiKey`, `mb_RegularApiKey`)
2. WAIT for the user to test and provide feedback
3. If they report issues, iterate (go back to Phase 2, then re-review in Phase 3 before asking the user again)
#### Phase 5: Open PR
When the user says they're happy (e.g., "looks good", "ship it", "done", "open the pr", "commit it"):
1. Stage and commit all fix-related changes:
- **NEVER commit changes under `.claude/`** — the worktree setup copies fixbot commands there, and those must not be committed
- **NEVER commit changes under `.fixbot/` or `.beads/` or `mage/`** — these are copied or generated files
- Stage files individually by name (`git add path/to/file.clj`) — do NOT use `git add .` or `git add -A`
- Only stage files that are part of the actual fix
- Do not include yourself as a co-author in the commit message
- **REMEMBER that the commit history is public and NO sensitive information should ever be stored in the git messages**
- **REMEMBER that the pull request is public and NO sensitive information should ever be stored in the pull request**
2. Push the branch to origin
3. Create the PR with `gh pr create`:
- Title: concise description of the fix
- **NEVER include Linear URLs or Linear issue IDs in the PR title, body, or commits** — Linear is internal
- Body should follow this template (do NOT include the backport/contributing sections from the repo's PR template):
```
### Description
<Describe the overall approach and the problem being solved>
### How to verify
<Step-by-step instructions to verify the fix>
### Checklist
- [x] Tests have been added/updated to cover changes in this PR
```
- Do NOT add any labels — that's up to the user
4. Tell the user the PR URL and a summary of what was fixed
#### Phase 6: Monitor PR
After submitting the pull request, monitor the pull request until it passes. NOTE: this may take a while and several attempts.
1. Run `/fixbot-ci` to monitor CI results and handle failures
2. **Ignore** the "Decide whether to backport or not" check failure — that's a label requirement handled by the user/reviewer, not something you can fix
3. **Small PRs:** For frontend-only changes (no backend files touched), the relevant CI checks are `frontend-tests/*` and `e2e-tests/*`. If those pass and only unrelated checks (SDK, driver tests, etc.) fail, re-run the failed jobs immediately rather than waiting for the full suite to complete.
### Status Bar
A status bar at the bottom of the tmux session shows issue info, service health, and your status message. The issue info and health indicators are managed automatically. You control only the status message by writing to `.fixbot/llm-status.txt`.
Write to `.fixbot/llm-status.txt` (overwrite the whole file each time) when something important changes — for example:
- Current phase of work (e.g., "Phase 1: Analyzing issue", "Phase 2: Writing tests", "Phase 3: Self-review", "Phase 4: Ready for user testing")
- A blocking question waiting on user input
- URLs the user needs (e.g., the page to test)
**Rules:**
- Keep it to **1-3 lines** — the pane is small and other info is displayed above your status
- Only update when the visible state meaningfully changes — don't spam updates
- Keep each line short and scannable
### Task Tracking with Beads
`bd` (beads) is installed and initialized in stealth mode for structured task tracking. Use it to break down complex work, track dependencies, and maintain context across phases.
**Key commands:**
- `bd create "Task title" -p 0` — create a task (lower priority number = higher priority)
- `bd ready` — list tasks that have no blockers and are ready to work on
- `bd update <id> --claim` — assign a task to yourself and mark it in-progress
- `bd update <id> --close` — mark a task as done
- `bd show <id>` — view task details and history
- `bd list` — list all tasks
**When to use:**
- When breaking a fix into multiple subtasks
- To track what's been done vs what remains
- To note blockers or dependencies between tasks
**Rules:**
- Beads is in stealth mode — it will not modify git state
- Don't overthink it — simple issues may not need task tracking at all
### nREPL
The backend runs an nREPL server. Connect using `localhost` and the nREPL port (read `NREPL_PORT` from `mise.local.toml`):
```bash
clj-nrepl-eval -H localhost -p $NREPL_PORT "(+ 1 2)"
```
Use nREPL for:
- Evaluating Clojure expressions against the running backend
- Requiring namespaces with `:reload` to pick up code changes
- Testing functions interactively
- Checking compilation
**REPL expression rules**: Send one expression per eval call. Multi-expression evals frequently timeout. Split into separate (parallel if independent) calls. Test/dev namespaces (`metabase.test`, `dev`, enterprise test namespaces) are available natively on the classpath.
### Browser Automation with Playwright MCP
A Playwright MCP server is configured in `.mcp.json` and provides browser automation tools directly as MCP tool calls (prefixed with `mcp__playwright__`). The browser runs headless outside the sandbox — no bash commands needed.
**This is optional.** For purely backend fixes where the issue and verification are API-level or logic-level, you don't need the browser at all — automated tests and nREPL are sufficient. Use the Playwright MCP tools when the issue involves UI behavior, when you need to see what the user sees, or when the user reports something that doesn't match what you'd expect from the code.
**Core workflow:**
1. `mcp__playwright__browser_navigate` — navigate to `http://localhost:$MB_JETTY_PORT`
2. `mcp__playwright__browser_snapshot` — capture page state with element refs (you MUST do this before any interaction)
3. Interact: `browser_click`, `browser_fill`, `browser_type`, `browser_select_option`, `browser_hover`, etc.
4. `mcp__playwright__browser_snapshot` again — verify the result
5. `mcp__playwright__browser_close` — close the browser when done
**Key MCP tools:**
- `browser_navigate` — navigate to a URL (`url` param)
- `browser_snapshot` — capture accessibility snapshot with element refs — **always snapshot before interacting**
- `browser_click` — click an element (`element` param: description from snapshot, `ref` param: element ref from snapshot)
- `browser_fill` — fill text into an input (`element`, `ref`, `value` params)
- `browser_type` — type text with keyboard events (`text` param, optional `element`/`ref`)
- `browser_select_option` — select a dropdown option (`element`, `ref`, `values` params)
- `browser_hover` — hover over an element (`element`, `ref` params)
- `browser_press_key` — press a keyboard key (`key` param)
- `browser_evaluate` — execute JavaScript (`script` param)
- `browser_console_messages` — get browser console logs
- `browser_network_requests` — list network activity
- `browser_take_screenshot` — capture visual screenshot (`raw` param for base64)
- `browser_close` — close the browser
- `browser_resize` — resize viewport (`width`, `height` params)
- `browser_navigate_back` / `browser_navigate_forward` — history navigation
- `browser_wait_for` — wait for a condition
**Login and navigate:**
1. Navigate to `http://localhost:$MB_JETTY_PORT/auth/login`
2. Use `browser_snapshot` to get element refs
3. Use `browser_fill` on the email and password fields
4. Use `browser_click` on the sign-in button
Alternatively, get a session token via API and use `browser_evaluate` to set a cookie:
```bash
curl -s -X POST http://localhost:$MB_JETTY_PORT/api/session \
-H 'Content-Type: application/json' \
-d '{"username":"admin@example.com","password":"admin123"}' | jq -r '.id'
```
Then use `browser_evaluate` with script: `document.cookie = 'metabase.SESSION=<token>;path=/'`
**"Start exploring" modal:** Metabase shows this modal on first native question view. Use `browser_snapshot` to find the button, then `browser_click` to dismiss it.
**When to use:**
- **Phase 1 (Understand):** If the issue involves UI behavior, reproduce it in the browser to see exactly what the user sees
- **Phase 4 (Verify):** Before asking the user to test, self-verify the fix by navigating to the affected page and confirming it works. Still ask the user for final sign-off — your browser check supplements but does not replace user acceptance testing.
- **Troubleshooting:** When API responses look correct but the user reports UI problems, use the browser to see what's actually rendering
**Rules:**
- If the Playwright MCP tools are not available or fail on first use, skip browser verification — do not spend time debugging MCP server issues.
- Always `browser_snapshot` before interacting — you need element refs to click/fill
- Always use `http://localhost:$MB_JETTY_PORT` (the backend port), never the frontend dev server port
- Close the browser when you're done to free resources
- If a page takes time to load, `browser_snapshot` again after a few seconds to get the updated state
### Server Logs
Read server output from log files in `.fixbot/`:
- **Backend**: `ls -t .fixbot/backend-*.log | head -1` to find the latest, then `tail -200 <file>`
- **Frontend**: `ls -t .fixbot/frontend-*.log | head -1` to find the latest, then `tail -200 <file>`
Check these when:
- Debugging startup failures (check if backend/frontend started successfully)
- Looking for error messages or stack traces
- Verifying that your code changes were picked up by hot reload
### GitHub API Workaround
If `gh` commands fail with `tls: failed to verify certificate: x509` errors, switch to using `curl` with `gh auth token` for the rest of the session. For example, to create a PR:
```bash
curl -s -X POST https://api.github.com/repos/metabase/metabase/pulls \
-H "Authorization: token $(gh auth token)" \
-H "Accept: application/vnd.github+json" \
-d '{ "title": "...", "head": "branch-name", "base": "master", "body": "..." }'
```
Use the same pattern for any other `gh` operations (e.g., `gh pr view` → `curl -s https://api.github.com/repos/metabase/metabase/pulls/NUMBER ...`).
### Important Rules
- **Python in bash:** Never write Python code inline in bash heredocs — `!` and other characters get mangled by shell expansion. Instead, write Python scripts to a temp file (`$TMPDIR/script.py`) and run them with `python3 $TMPDIR/script.py`.
- Focus ONLY on the reported issue — no unrelated changes
- Always run tests before telling the user to verify
- Check backend readiness: `curl -s http://localhost:$MB_JETTY_PORT/api/health`
- Be patient — the backend takes several minutes to start on first launch. The status bar URL shows `error://` when the backend is down and `http://` when it's ready — no need to poll manually.
- Work autonomously — do not block on the user for technical questions. Research the codebase, read tests, and make your own decisions.
- Only involve the user for product/behavior questions and acceptance testing

4
.claude/fixbot/fixbot-xdg-open Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
# Proxy for xdg-open that works on macOS host.
# Used by Claude Code inside the sandbox to open browser for OAuth login.
exec open "$@"

View File

@@ -0,0 +1,29 @@
{
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": true,
"allowUnsandboxedCommands": true,
"filesystem": {
"allowWrite": [".", "{{pwd}}", "~/.m2", "~/.gitlibs", "~/.deps.clj", "~/.local/state/workmux", "~/.cache/workmux", "{{tmpdir}}", "/tmp", "/private/tmp", "~/Library/Caches/ms-playwright", "~/Library/Application Support/Google/Chrome"],
"allowRead": [".", "{{pwd}}", "~/.m2", "~/.gitlibs", "~/.deps.clj", "~/.local/state/workmux", "~/.cache/workmux", "{{tmpdir}}", "/tmp", "/private/tmp", "~/Library/Caches/ms-playwright", "~/Library/Application Support/Google/Chrome"]
},
"excludedCommands": ["docker"],
"network": {
"allowLocalBinding": true,
"allowAllUnixSockets": true,
"allowedDomains": ["github.com", "api.github.com", "*.blob.core.windows.net"]
}
},
"permissions": {
"allow": [
"Edit",
"Write",
"Skill",
"Grep",
"mcp__playwright__*",
"WebFetch(domain:linear.app)",
"WebFetch(domain:metabase.com)"
]
},
"skipDangerousModePermissionPrompt": true
}

View File

@@ -0,0 +1,43 @@
agent: claude
files:
symlink:
- node_modules
- bin/bb
post_create:
# Copy fixbot tooling from source repo (worktree is based on master, not possibly-different)
- cp -r {{SOURCE_REPO}}/.claude/fixbot .claude/fixbot
- cp -r {{SOURCE_REPO}}/mage/src/mage/fixbot mage/src/mage/fixbot
- cp {{SOURCE_REPO}}/bb.edn bb.edn
- sed "s|{{pwd}}|$(pwd)|;s|{{tmpdir}}|${TMPDIR%/}|" .claude/fixbot/settings.local-template.json > .claude/settings.local.json
- mkdir -p .fixbot/playwright/sessions .fixbot/playwright/sockets
- echo '{{ISSUE_ID}} | {{ISSUE_URL}}' > .fixbot/issue.txt
- "echo 'Phase 0: Setting up' > .fixbot/llm-status.txt"
- rm -f .claude/commands/fixbot*.md
- cp .claude/fixbot/commands/*.md .claude/commands/
- mkdir -p .github/hooks/workmux-status
- echo '{"version":1,"hooks":{"userPromptSubmitted":[{"type":"command","bash":"workmux set-window-status working"}],"postToolUse":[{"type":"command","bash":"workmux set-window-status working"}],"agentStop":[{"type":"command","bash":"workmux set-window-status done"}]}}' > .github/hooks/workmux-status/hooks.json
- MB_PREMIUM_EMBEDDING_TOKEN={{MB_PREMIUM_EMBEDDING_TOKEN}} LINEAR_API_KEY={{LINEAR_API_KEY}} ./bin/mage -fixbot-dev-env --app-db {{APP_DB}}
- mise trust mise.local.toml
- bun install
- 'echo ''{"mcpServers":{"playwright":{"command":"npx","args":["-y","@playwright/mcp","--headless","--browser","chrome"]}}}'' > .mcp.json'
pre_remove:
- ./bin/mage -fixbot-dev-env --down
panes:
- command: <agent>
focus: true
- command: mise x -- clj -M:dev:dev-start:drivers:drivers-dev:ee:ee-dev 2>&1 | tee .fixbot/backend-$(date +%Y%m%d-%H%M%S).log
split: horizontal
percentage: 30
- command: mise x -- bun run build-hot 2>&1 | tee .fixbot/frontend-$(date +%Y%m%d-%H%M%S).log
split: vertical
- command: mise x -- ./bin/mage -fixbot-status
split: vertical
size: 7

View File

@@ -1,4 +1,4 @@
---
--b-
name: add-tracing
description: Add OpenTelemetry tracing spans to Clojure code following Metabase tracing conventions. Use when instrumenting backend code with trace coverage.
---

View File

@@ -176,10 +176,8 @@
clojure.core.async/to-chan!
clojure.core.async/to-chan!!
metabase.channel.core/send!
metabase.warehouses.models.database/assert-not-h2!
metabase.driver.sql-jdbc.execute/execute-prepared-statement!
metabase.notification.models/create-notification!
metabase.oauth-server.core/reset-provider!
metabase.pulse.send/send-pulse!
metabase-enterprise.transforms-python.python-runner-test/execute!
;; Workspace isolation functions are safe in parallel tests because they operate on
@@ -554,7 +552,6 @@
metabase-enterprise.cache.config-test/with-temp-persist-models hooks.common/with-seven-bindings
metabase-enterprise.dependencies.api-test/with-dependents-test! hooks.common/with-two-bindings
metabase-enterprise.metabot-v3.tools.dependencies-test/with-dependent-transforms! hooks.common/with-two-bindings
metabase-enterprise.metabot.tools.dependencies-test/with-dependent-transforms! hooks.common/with-two-bindings
metabase-enterprise.semantic-search.db.connection/with-migrate-tx hooks.common/let-one-with-optional-value
metabase-enterprise.serialization.api-test/with-serialization-test-data! hooks.common/with-three-bindings
metabase-enterprise.serialization.test-util/with-temp-dpc hooks.toucan2.tools.with-temp/with-temp
@@ -596,9 +593,6 @@
metabase.model-persistence.api-test/with-persistence-setup! hooks.common/with-one-top-level-binding
metabase.model-persistence.api-test/with-setup! hooks.common/with-one-top-level-binding
metabase.model-persistence.test-util/with-persistence-enabled! hooks.metabase.model-persistence.test-util/with-persistence-enabled!
metabase.mq.core/with-queue hooks.common/let-second
metabase.mq.core/with-topic hooks.common/let-second
metabase.mq.core/def-listener! hooks.metabase.mq.impl/def-listener
metabase.models.interface/define-batched-hydration-method hooks.metabase.models.interface/define-hydration-method
metabase.models.interface/define-simple-hydration-method hooks.metabase.models.interface/define-hydration-method
metabase.permissions.core/allow-changing-all-users-group-members hooks.common/do*

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +0,0 @@
(ns hooks.metabase.mq.impl
(:require
[clj-kondo.hooks-api :as hooks]))
(defn def-listener
"Kondo hook for `mq/def-listener`. Rewrites to a `fn` form so kondo can analyze bindings.
Forms:
(mq/def-listener! :topic/foo [msg] body...)
(mq/def-listener! :queue/bar {:config map} [msgs] body...)"
[{:keys [node]}]
(let [[_ _channel-name & args] (:children node)
;; skip optional config map
args (if (hooks/map-node? (first args))
(rest args)
args)
[bindings & body] args
;; rewrite as: (fn [bindings...] body...)
new-node (hooks/list-node
(list*
(hooks/token-node 'fn)
bindings
body))]
{:node new-node}))

View File

@@ -303,6 +303,7 @@
metabase.util.malli/with [[:default]]
metabase.util.malli/with-api-error-message [[:default]]
metabase.util.namespaces/import-fns [[:default]]
metabase.util.queue-test/await-test-while [[:block 1]]
metabase.util/auto-retry [[:block 1]]
metabase.util/case-enum [[:block 1]]
metabase.util/for-map [[:block 1]]

6
.github/team.json vendored
View File

@@ -1,13 +1,13 @@
{
"teams": [
{
"name": "Querying Platform",
"label": ".Team/QueryingPlatform",
"name": "Querying",
"label": ".Team/Querying",
"members": [
"bshepherdson",
"metamben",
"rileythomp",
"camsaul"
"camsual"
]
},
{

View File

@@ -37,16 +37,16 @@ jobs:
name: Run Eastwood linter
be-linter-eastwood-test:
if: ${{ !inputs.skip && github.ref_name == 'master' }}
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 30
name: Eastwood (tests only)
name: Eastwood (test)
steps:
- uses: actions/checkout@v4
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- run: clojure -X:dev:ee:ee-dev:drivers:drivers-dev:test:eastwood/test
name: Run Eastwood linter on our backend tests
name: Run Eastwood linter on tests
be-tests:
if: ${{ !inputs.skip }}

View File

@@ -0,0 +1,360 @@
name: "Claude Code Bug Fix"
on:
issues:
types:
- labeled
pull_request_review:
types:
- submitted
workflow_dispatch:
inputs:
issue_number:
description: "Issue number to fix"
required: true
type: string
override_employee_check:
description: "Override employee check for issue creator"
required: false
default: false
type: boolean
jobs:
auto-fix-bug:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 30
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
if: |
(github.event_name == 'issues' && github.event.label.name == 'Claude Bug Fix' && github.event.issue.state == 'open') ||
(github.event_name == 'workflow_dispatch') ||
(github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested' && startsWith(github.event.pull_request.head.ref, 'claude-fix/'))
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get issue data and check employee status
id: get_issue
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { getIssueData } = require('${{ github.workspace }}/.github/scripts/get-issue-data.js');
try {
const result = await getIssueData({
context,
github,
manualIssueNumber: '${{ github.event.inputs.issue_number }}'
});
const { issueNumber, issueData, employeeComments, prReviews, triggerType, isIssueAuthorEmployee } = result;
// Check employee status and fail if not authorized
if (!result.isIssueAuthorEmployee && '${{ github.event.inputs.override_employee_check }}' !== 'true') {
core.setFailed(`User ${result.issueData.user.login} is not a Metabase employee. Skipping bug fix automation.`);
}
core.setOutput('issue_number', issueNumber);
core.setOutput('issue_data', JSON.stringify(issueData));
core.setOutput('issue_comments', JSON.stringify(employeeComments));
core.setOutput('pr_reviews', JSON.stringify(prReviews));
core.setOutput('trigger_type', triggerType);
} catch (error) {
core.setFailed(error.message);
}
- name: Find linked Linear issue
id: find_linear
continue-on-error: true
uses: actions/github-script@v7
env:
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
ISSUE_NUMBER: ${{ steps.get_issue.outputs.issue_number }}
with:
script: |
const { findLinearIssue } = require('${{ github.workspace }}/.github/scripts/find-linear-issue.js');
const githubIssueUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/issues/${process.env.ISSUE_NUMBER}`;
const result = await findLinearIssue({
githubIssueUrl,
linearApiKey: process.env.LINEAR_API_KEY
});
if (result.success) {
console.log(`Found Linear issue: ${result.issue}`);
core.setOutput('linear_issue', result.issue);
} else {
console.log(`Could not find Linear issue: ${result.message}`);
}
- name: Set up git configuration
run: |
git config --global user.name "Claude Code[bot]"
git config --global user.email "noreply@anthropic.com"
- name: Prepare issue context and bug fix prompt
id: prepare_prompt
uses: actions/github-script@v7
env:
ISSUE_DATA: ${{ steps.get_issue.outputs.issue_data }}
ISSUE_COMMENTS: ${{ steps.get_issue.outputs.issue_comments }}
PR_REVIEWS: ${{ steps.get_issue.outputs.pr_reviews }}
TRIGGER_TYPE: ${{ steps.get_issue.outputs.trigger_type }}
with:
script: |
const issueData = JSON.parse(process.env.ISSUE_DATA);
const issueComments = JSON.parse(process.env.ISSUE_COMMENTS);
const prReviews = JSON.parse(process.env.PR_REVIEWS);
const triggerType = process.env.TRIGGER_TYPE;
const issueNumber = '${{ steps.get_issue.outputs.issue_number }}';
// Convert issue title to kebab case and create branch name
const issueTitle = issueData.title || 'untitled';
const kebabTitle = issueTitle
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.replace(/-+/g, '-')
.substring(0, 50);
const branchName = `claude-fix/issue-${issueNumber}-${kebabTitle}`;
// Format data for bash environment
const formattedComments = issueComments.length > 0
? issueComments.map(comment =>
`**Comment by @${comment.user.login} on ${comment.created_at}:**\n${comment.body}`
).join('\n\n---\n\n')
: '';
const formattedReviews = prReviews.length > 0
? prReviews.map(review =>
`**Review by @${review.user.login} on ${review.submitted_at} (Changes Requested):**\n${review.body || 'No review comment provided'}`
).join('\n\n---\n\n')
: '';
core.exportVariable('BRANCH_NAME', branchName);
core.exportVariable('ISSUE_NUMBER', issueNumber);
core.exportVariable('ISSUE_TITLE', issueData.title);
core.exportVariable('ISSUE_AUTHOR', issueData.user.login);
core.exportVariable('ISSUE_CREATED', issueData.created_at);
core.exportVariable('ISSUE_BODY', issueData.body || 'No description provided');
core.exportVariable('ISSUE_COMMENTS_FORMATTED', formattedComments);
core.exportVariable('PR_REVIEWS_FORMATTED', formattedReviews);
core.exportVariable('TRIGGER_TYPE', triggerType);
- name: Generate bug fix prompt
run: |
cat << 'EOF' >> $GITHUB_ENV
BUG_FIX_PROMPT<<PROMPT_END
You are helping to fix a bug reported in GitHub issue #${{ env.ISSUE_NUMBER }}.
## Bug Report Details
**Title:** ${{ env.ISSUE_TITLE }}
**Reporter:** ${{ env.ISSUE_AUTHOR }}
**Created:** ${{ env.ISSUE_CREATED }}
**Description:**
${{ env.ISSUE_BODY }}
**Comments:**
${{ env.ISSUE_COMMENTS_FORMATTED }}
EOF
# Add review feedback section only if this is a review trigger
if [ "${{ env.TRIGGER_TYPE }}" = "pull_request_review" ]; then
cat << 'EOF' >> $GITHUB_ENV
## Previous Fix Attempt - Review Feedback (MUST ADDRESS)
Your previous automated fix attempt received the following feedback that needs to be addressed:
${{ env.PR_REVIEWS_FORMATTED }}
EOF
fi
# Add the rest of the prompt (always the same)
cat << 'EOF' >> $GITHUB_ENV
## Instructions
1. **Analyze the issue description and comments** - Understand what the issue is describing
a. Pay particular attention to reproduction steps and if there's an agreed upon fix.
2. **Search the codebase** - Use Grep and Glob tools to find relevant files
3. **Implement a fix** - Use the appropriate write skill based on the file types:
- Use clojure-write for Clojure/ClojureScript files
- Use typescript-write for TypeScript/JavaScript files
- Follow the coding standards and patterns in each skill
4. **Test the fix** - Run appropriate tests as specified in the skills
5. **Verify the fix** - Ensure tests pass and code follows conventions
## Important Guidelines
- Use the appropriate write skill for the language you're working in
- Write failing tests first, then fix them
- Work in small, testable increments
- Don't commit changes - leave that for the workflow
- IMPORTANT: Focus only on fixing the reported bug, don't make unrelated changes
## Scratchpad Directory
A `.claude-scratchpad/` directory has been created for your temporary files:
- Use this directory for ALL temporary verification files. However, prefer using production test files rather than adhoc ones.
- Only modify actual production code in the main repository directories
- The scratchpad will be cleaned up automatically - nothing from it will be committed
Start by understanding the bug and then systematically work toward a solution.
PROMPT_END
EOF
- name: Determine branch strategy
run: |
BRANCH_NAME="${{ env.BRANCH_NAME }}"
if git branch -r --list "origin/${BRANCH_NAME}" | grep -q .; then
echo "Branch already exists: $BRANCH_NAME"
echo "Will push to existing branch and skip PR creation"
echo "BRANCH_EXISTS=true" >> $GITHUB_ENV
else
echo "Creating new branch: $BRANCH_NAME"
echo "Will create new PR"
echo "BRANCH_EXISTS=false" >> $GITHUB_ENV
fi
# Export the branch name
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
- name: Create scratchpad directory for Claude
run: |
# Create a scratchpad directory for Claude to use for temporary files
mkdir -p .claude-scratchpad
echo "Scratchpad directory created for Claude's temporary verification files"
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Generate GitHub App Token for Claude
uses: actions/create-github-app-token@v1
id: claude-app-token
with:
app-id: ${{ secrets.METABASE_BOT_APP_ID }}
private-key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
- name: Generate Bug Fix with Claude Code
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ steps.claude-app-token.outputs.token }}
prompt: ${{ env.BUG_FIX_PROMPT }}
claude_args: |
--allowedTools "Bash(bun run test-unit*),Bash(bun run lint-eslint-pure),Bash(./bin/mage*),Bash(clojure*),Bash(find*),Bash(ls*),Bash(grep*),Write,Read,Glob,Grep,Edit,MultiEdit"
--disallowedTools "Bash(git commit*),Bash(git push*),Bash(gh*)"
- name: Clean up scratchpad directory
run: |
# Remove the scratchpad directory so its contents don't get committed
rm -rf .claude-scratchpad/
# Remove output file generated by Claude Code action
rm -f output.txt
echo "Scratchpad directory and action output files cleaned up"
- name: Check for changes and analysis
id: check_changes
run: |
# Now check for actual code changes (excluding analysis file)
if [ -n "$(git status --porcelain)" ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "Changes detected:"
git status --short
else
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "No changes made by Claude."
fi
- name: Get issue team labels and prepare reviewers
if: steps.check_changes.outputs.has_changes == 'true'
id: prepare_reviewers
uses: actions/github-script@v7
env:
ISSUE_DATA: ${{ steps.get_issue.outputs.issue_data }}
with:
script: |
const issueData = JSON.parse(process.env.ISSUE_DATA);
const issueLabels = issueData.labels.map(label => label.name);
const teamLabels = issueLabels.filter(label => label.startsWith('.Team/'));
const issueCreator = issueData.user.login;
core.setOutput('team_labels', teamLabels.join(','));
core.setOutput('reviewers', issueCreator);
- name: Create Pull Request
id: create_pr
if: steps.check_changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ env.BRANCH_NAME }}
base: master
commit-message: |
Fix: ${{ env.ISSUE_TITLE }}
Fixes #${{ env.ISSUE_NUMBER }}
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
committer: "Claude Code[bot] <noreply@anthropic.com>"
author: "Claude Code[bot] <noreply@anthropic.com>"
title: "Fix: ${{ env.ISSUE_TITLE }}"
body: |
This PR automatically fixes the bug reported in #${{ env.ISSUE_NUMBER }}.
${{ steps.find_linear.outputs.linear_issue && format('Closes {0}', steps.find_linear.outputs.linear_issue) || '' }}
## Bug Report
**Original Issue:** #${{ env.ISSUE_NUMBER }}
**Reporter:** @${{ env.ISSUE_AUTHOR }}
**Issue Title:** ${{ env.ISSUE_TITLE }}
## Automated Fix
🤖 This fix was generated automatically by Claude Code based on the issue description.
**Please review carefully:**
- [ ] The fix addresses the reported issue
- [ ] Tests pass and code follows conventions
- [ ] No unrelated changes were introduced
- [ ] The solution is appropriate and maintainable
## Next Steps
- Review the changes and test thoroughly
- If the fix looks good, approve and merge
- If issues are found, either fix them or close this PR
---
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
labels: |
${{ steps.prepare_reviewers.outputs.team_labels }}
automated-fix
ci skip
reviewers: ${{ steps.prepare_reviewers.outputs.reviewers }}
assignees: ${{ github.actor }}
- name: Log existing branch update
if: steps.check_changes.outputs.has_changes == 'true' && env.BRANCH_EXISTS == 'true'
uses: actions/github-script@v7
with:
script: |
console.log('🔄 Updated existing branch: ${{ env.BRANCH_NAME }}');
console.log('The changes have been pushed to the existing PR for this issue.');
- name: Log analysis for no changes
if: steps.check_changes.outputs.has_changes == 'false'
uses: actions/github-script@v7
with:
script: |
console.log('🤖 No code changes were made by Claude.');

View File

@@ -76,7 +76,7 @@ jobs:
with:
ref: ${{ github.event.inputs.commit || github.event.pull_request.head.sha || github.sha }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
uses: aquasecurity/trivy-action@0.29.0
env:
TRIVY_OFFLINE_SCAN: true
repo: ${{ fromJson(needs.get-container-info.outputs[matrix.edition]).repo }}
@@ -85,6 +85,7 @@ jobs:
image-ref: docker.io/${{ env.repo }}:${{ env.tag }}
format: sarif
output: trivy-results.sarif
version: "v0.57.1"
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:

View File

@@ -154,6 +154,7 @@ jobs:
services:
mariadb:
image: metabase/mariadb:10.6 # https://github.com/metabase/metabase-mariadb-docker-image
image: ${{ matrix.version.image }}
ports:
- "3306:3306"
env:

View File

@@ -61,38 +61,13 @@ jobs:
- name: Prepare release scripts
uses: ./.github/actions/prepare-release-scripts
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare Cypress environment
uses: ./.github/actions/prepare-cypress
- name: Run cross-version test
env:
CURRENT_VERSION: ${{ vars.CURRENT_VERSION }}
RESULT_FILE: ${{ runner.temp }}/cross-version-result-${{ matrix.source }}-${{ matrix.target }}.json
run: |
cd cross-version
./test.sh --source "${{ matrix.source }}" --target "${{ matrix.target }}"
- name: Upload failure result
uses: actions/upload-artifact@v4
if: failure()
with:
name: failure-result-${{ matrix.source }}-${{ matrix.target }}
path: ${{ runner.temp }}/cross-version-result-${{ matrix.source }}-${{ matrix.target }}.json
if-no-files-found: ignore
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-recording-${{ matrix.source }}-${{ matrix.target }}
path: |
./cypress
./logs/test.log
if-no-files-found: ignore
- name: Show logs on failure
if: failure()
run: |
@@ -100,84 +75,6 @@ jobs:
docker compose logs metabase || true
docker compose logs postgres || true
# Notify Slack on nightly failures
notify-on-failure:
if: always() && github.event_name == 'schedule' && needs.test-matrix.result == 'failure'
needs: [test-matrix]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
release
.github
- name: Prepare release scripts
uses: ./.github/actions/prepare-release-scripts
- name: Download failure results
uses: actions/download-artifact@v4
continue-on-error: true
with:
pattern: failure-result-*
path: failure-results
merge-multiple: true
- name: Notify Slack
continue-on-error: true
uses: actions/github-script@v7
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
with:
script: |
const fs = require('fs');
const path = require('path');
const { buildFailureResults, sendCrossVersionSlackNotification } = require('${{ github.workspace }}/release/dist/index.cjs');
// Get all failed jobs from the GitHub API
const { owner, repo } = context.repo;
const jobInfo = await github.rest.actions.listJobsForWorkflowRun({
owner,
repo,
run_id: context.runId,
per_page: 100,
});
const failedJobs = jobInfo.data.jobs
.filter(job => job.status === 'completed' && job.conclusion === 'failure' && job.name.startsWith('test-matrix'))
.map(job => ({ name: job.name, url: job.html_url }));
if (failedJobs.length === 0) {
console.log('No failed test-matrix jobs found');
return;
}
// Load artifact data for enrichment (phase)
const resultsDir = 'failure-results';
const artifactData = new Map();
if (fs.existsSync(resultsDir)) {
for (const file of fs.readdirSync(resultsDir)) {
if (!file.endsWith('.json')) continue;
try {
const result = JSON.parse(fs.readFileSync(path.join(resultsDir, file), 'utf8'));
artifactData.set(`${result.source}-${result.target}`, result);
} catch (e) {
console.log(`Skipping malformed result: ${file}`);
}
}
}
const failures = buildFailureResults(failedJobs, artifactData);
const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${context.runId}`;
const slackBotToken = process.env.SLACK_BOT_TOKEN;
if (!slackBotToken) {
console.log('SLACK_BOT_TOKEN is not set; skipping Slack notification.');
return;
}
await sendCrossVersionSlackNotification(failures, runUrl, slackBotToken);
console.log('Slack notification sent');
# Manual test for workflow_dispatch
test-manual:
if: github.event_name == 'workflow_dispatch'
@@ -190,12 +87,6 @@ jobs:
- name: Prepare release scripts
uses: ./.github/actions/prepare-release-scripts
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare Cypress environment
uses: ./.github/actions/prepare-cypress
- name: Run cross-version test
env:
CURRENT_VERSION: ${{ vars.CURRENT_VERSION }}
@@ -203,16 +94,6 @@ jobs:
cd cross-version
./test.sh --source "${{ inputs.source_version }}" --target "${{ inputs.target_version }}"
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-recording-${{ inputs.source_version }}-${{ inputs.target_version }}
path: |
./cypress
./logs/test.log
if-no-files-found: ignore
- name: Show logs on failure
if: failure()
run: |

View File

@@ -2,7 +2,7 @@ name: Auto-update documentation (master)
on:
schedule:
- cron: "0 21 * * 1-5" # Every Monday through Friday at 9 PM UTC
- cron: "0 1 * * 1-5" # Every Monday through Friday at 1 AM UTC
workflow_dispatch:
jobs:

View File

@@ -2,7 +2,7 @@ name: Auto-update documentation (release)
on:
schedule:
- cron: "0 22 * * 1-5" # Every Monday through Friday at 10 PM UTC
- cron: "0 2 * * 1-5" # Every Monday through Friday at 2 AM UTC
workflow_dispatch:
inputs:
target_branch:

View File

@@ -31,11 +31,6 @@ on:
type: boolean
required: false
default: true
fail_fast:
description: "Stop running remaining tests after the first failure"
type: boolean
required: false
default: false
jobs:
determine-test-type:
@@ -72,7 +67,6 @@ jobs:
echo '- `grep`: "${{ inputs.grep }}"' >> $GITHUB_STEP_SUMMARY
echo '- `qa_db`: "${{ inputs.qa_db }}"' >> $GITHUB_STEP_SUMMARY
echo '- `enable_network_throttling`: "${{ inputs.enable_network_throttling }}"' >> $GITHUB_STEP_SUMMARY
echo '- `fail_fast`: "${{ inputs.fail_fast }}"' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo 'triggered by: @${{ github.event.sender.login }}' >> $GITHUB_STEP_SUMMARY
@@ -150,7 +144,6 @@ jobs:
env:
GREP: ${{ github.event.inputs.grep }}
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
FAIL_FAST: ${{ inputs.fail_fast }}
run: |
node e2e/runner/run_cypress_ci.js ${{ needs.determine-test-type.outputs.type }} \
--spec '${{ github.event.inputs.spec }}' \

View File

@@ -88,30 +88,22 @@ jobs:
timeout-minutes: 5
steps:
- name: Generate workflow summary
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
STORY_FILES: ${{ needs.detect-changed-stories.outputs.story_files }}
BURN_IN: ${{ inputs.burn_in || 50 }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_ACTOR: ${{ github.event.pull_request.user.login }}
ACTOR: ${{ github.actor }}
run: |
echo '## Loki Stress Test' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo "**Branch:** \`${BRANCH_NAME}\`" >> $GITHUB_STEP_SUMMARY
echo '**Branch:** `${{ github.head_ref || github.ref_name }}`' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo '**Story files:**' >> $GITHUB_STEP_SUMMARY
echo "${STORY_FILES}" | tr ',' '\n' | while read -r file; do
echo '${{ needs.detect-changed-stories.outputs.story_files }}' | tr ',' '\n' | while read -r file; do
echo "- \`$file\`" >> $GITHUB_STEP_SUMMARY
done
echo '' >> $GITHUB_STEP_SUMMARY
echo "**Burn-in:** ${BURN_IN} runs" >> $GITHUB_STEP_SUMMARY
echo '**Burn-in:** ${{ inputs.burn_in || 50 }} runs' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
if [[ "${EVENT_NAME}" == "pull_request" ]]; then
echo "Triggered by PR #${PR_NUMBER} from @${PR_ACTOR}" >> $GITHUB_STEP_SUMMARY
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo 'Triggered by PR #${{ github.event.pull_request.number }} from @${{ github.event.pull_request.user.login }}' >> $GITHUB_STEP_SUMMARY
else
echo "Triggered manually by @${ACTOR}" >> $GITHUB_STEP_SUMMARY
echo 'Triggered manually by @${{ github.actor }}' >> $GITHUB_STEP_SUMMARY
fi
stress-test-loki:

View File

@@ -20,7 +20,6 @@ jobs:
NAMED_MODULES=$(node --input-type=module -e "
import { elements } from './frontend/lint/module-boundaries.mjs';
const named = elements.filter(e => !['shared/other', 'app/misc', 'other'].includes(e.type));
console.log(named.length);
")
NAMED_DIRS=$(node --input-type=module -e "
@@ -30,7 +29,6 @@ jobs:
const m = el.pattern.match(/^frontend\/src\/metabase\/(\w[\w-]*)\//)
if (m) dirs.add(m[1]);
}
for (const d of dirs) console.log(d);
")
TOTAL_DIRS=$(ls -d frontend/src/metabase/*/ | xargs -I{} basename {} | wc -l | tr -d ' ')
NAMED_COUNT=$(echo "$NAMED_DIRS" | grep -c . || true)

View File

@@ -13,35 +13,32 @@ jobs:
(github.event.label.name == 'Priority:P0' || github.event.label.name == 'Priority:P1' || github.event.label.name == '.Escalation') &&
contains(github.event.issue.labels.*.name, '.Team/Querying')
steps:
- name: Setting title
uses: actions/github-script@v7
id: vars
with:
script: |
core.setOutput('issue_title', ${{ toJson(github.event.issue.title) }}.replaceAll(/"/g, '\\"'));
- name: Trigger PagerDuty Incident
env:
ROUTING_KEY: ${{ secrets.QUERYING_PAGERDUTY_INTEGRATION_KEY }}
ISSUE_TITLE: ${{ github.event.issue.title }}
LABEL_NAME: ${{ github.event.label.name }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_URL: ${{ github.event.issue.html_url }}
REPO: ${{ github.repository }}
ALL_LABELS: ${{ join(github.event.issue.labels.*.name, ', ') }}
run: |
payload=$(jq -n \
--arg rk "$ROUTING_KEY" \
--arg dk "github-issue-$ISSUE_NUMBER" \
--arg sum "[$LABEL_NAME] $ISSUE_TITLE" \
--arg src "GitHub Issues - $REPO" \
--arg lbl "$ALL_LABELS" \
--arg url "$ISSUE_URL" \
'{
routing_key: $rk,
event_action: "trigger",
dedup_key: $dk,
payload: {
summary: $sum,
source: $src,
severity: "critical",
custom_details: { Labels: $lbl }
},
links: [{ href: $url, text: "View GitHub Issue" }]
}')
curl -X POST https://events.pagerduty.com/v2/enqueue \
-H 'Content-Type: application/json' \
-d "$payload"
-d '{
"routing_key": "${{ secrets.QUERYING_PAGERDUTY_INTEGRATION_KEY }}",
"event_action": "trigger",
"dedup_key": "github-issue-${{ github.event.issue.number }}",
"payload": {
"summary": "[${{ github.event.label.name }}] ${{ steps.vars.outputs.issue_title }}",
"source": "GitHub Issues - ${{ github.repository }}",
"severity": "critical",
"custom_details": {
"Labels": "${{ join(github.event.issue.labels.*.name, ', ') }}"
}
},
"links": [
{
"href": "${{ github.event.issue.html_url }}",
"text": "View GitHub Issue"
}
]
}'

View File

@@ -1,68 +0,0 @@
name: Release Custom Viz Package
run-name: Release @metabase/custom-viz v${{ inputs.version }} from ${{ inputs.branch }}
on:
workflow_dispatch:
inputs:
branch:
description: "Branch to release from"
type: string
required: true
default: "custom-viz"
version:
description: "Version to publish (e.g. 0.1.0)"
type: string
required: true
npm_tag:
description: "NPM dist-tag"
type: choice
required: true
default: "latest"
options:
- latest
- next
- canary
jobs:
build-and-publish:
runs-on: ubuntu-22.04
timeout-minutes: 20
defaults:
run:
working-directory: frontend/src/custom-viz
steps:
- name: Check out the code
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch }}
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Setup npm registry auth
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
registry-url: "https://registry.npmjs.org"
- name: Set package version
run: npm version ${{ inputs.version }} --no-git-tag-version
- name: Build package
run: bun run build
- name: Publish to NPM
run: npm publish --tag ${{ inputs.npm_tag }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_RELEASE_TOKEN }}
- name: Generate workflow summary
run: |
tee -a $GITHUB_STEP_SUMMARY << EOF
## Published @metabase/custom-viz
- **Version**: ${{ inputs.version }}
- **Tag**: ${{ inputs.npm_tag }}
- **Branch**: ${{ inputs.branch }}
- **Triggered by**: @${{ github.event.sender.login }}
EOF

View File

@@ -13,13 +13,13 @@ on:
type: string
required: false
default: ""
schedule:
- cron: "0 21 * * 1-5" # every weekday at 4 pm EST/5 pm EDT
# schedule:
# - cron: "0 21 * * 1-5" # every weekday at 4 pm EST/5 pm EDT
jobs:
auto-patch-trigger:
# don't run this workflow on a cron for forks
if: ${{ github.event_name != 'schedule' || (github.repository == 'metabase/metabase' && vars.ENABLE_AUTO_PATCH == 'true') }}
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
steps:

View File

@@ -110,7 +110,7 @@ jobs:
sdk-tests:
needs: [uberjar, embedding-sdk-package, files-changed]
if: always() && !cancelled()
if: always()
uses: ./.github/workflows/embedding-sdk.yml
secrets: inherit
with:

View File

@@ -192,42 +192,6 @@ jobs:
./COMMIT-ID
./SHA256.sum
upload-to-cloud:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
needs: tag-uberjar-for-release
timeout-minutes: 15
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
- uses: actions/checkout@v4
with:
sparse-checkout: .github
- name: Retrieve release ee uberjar artifact
uses: ./.github/actions/fetch-artifact
with:
commit: ${{ inputs.commit }}
edition: ee
release: "true"
- name: Determine the upload path
uses: actions/github-script@v7
id: version_path
with:
result-encoding: string
script: | # js
const version = '${{ inputs.version }}';
// only EE version
const version_path = 'release/' + version.replace(/^v0\./, "v1.");
console.log("sending to", version_path);
return version_path;
- name: Upload to S3
run: |
aws s3 cp ./metabase.jar s3://${{ secrets.AWS_S3_CLOUD_DEPLOY_BUCKET }}/${{ steps.version_path.outputs.result }}/metabase.jar
run-pre-release-tests:
name: Run pre-release tests for ${{ inputs.version }}
needs: tag-uberjar-for-release

View File

@@ -19,7 +19,7 @@ jobs:
id: vars
with:
script: |
core.setOutput('issue_title', JSON.stringify(${{ toJson(github.event.issue.title) }} + '\n' + ${{ toJson(github.event.issue.html_url) }}));
core.setOutput('issue_title', ${{ toJson(github.event.issue.title) }}.replaceAll(/"/g, '\\"'));
- name: Extracting team name
id: team_name
run: |
@@ -39,7 +39,7 @@ jobs:
"type": "section",
"text": {
"type": "mrkdwn",
"text": ${{ steps.vars.outputs.issue_title }}
"text": "${{ steps.vars.outputs.issue_title }}\n${{ github.event.issue.html_url }}"
}
}
]

5
.gitignore vendored
View File

@@ -13,6 +13,8 @@
.\#*
.cpcache/
.eastwood
/.beads/
/.fixbot/
.idea/
.nrepl-port
.portal
@@ -96,7 +98,7 @@ coverage-summary.json
junit.xml
pom.xml
pom.xml.asc
/profiles.clj
profiles.clj
target/checksum.txt
xcshareddata
xcuserdata
@@ -176,4 +178,3 @@ CLAUDE.local.md
/resources/python-sources/sqlglot-*.dist-info/
/resources/python-sources/.lock
__pycache__/
.mcp.json

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

View File

@@ -6,7 +6,7 @@ For detailed guidance on writing and reviewing code and documentation, see the s
## Clojure
- **[clojure-eval](.claude/skills/clojure-eval/SKILL.md)** - Always use this to evaluate Clojure code, **run tests**, and verify edits/compile. Prefer this over shell commands.
- **[clojure-eval](.claude/skills/clojure-eval/SKILL.md)** - Always use this to evaluate Clojure code, **run tests**, and verify edits/compile. Prefer this over `clojure -X:...` shell commands.
- **[clojure-write](.claude/skills/clojure-write/SKILL.md)** - Clojure/ClojureScript development with REPL-driven workflow and coding conventions
- **[clojure-review](.claude/skills/clojure-review/SKILL.md)** - Clojure/ClojureScript code review guidelines and style enforcement

View File

@@ -42,9 +42,6 @@ FROM eclipse-temurin:21-jre-alpine AS runner
ENV FC_LANG=en-US LC_CTYPE=en_US.UTF-8
# copy certs before the RUN so keytool can import them
COPY bin/docker/DigiCertGlobalRootG2.crt.pem /app/certs/DigiCertGlobalRootG2.crt.pem
# dependencies
RUN apk add -U bash fontconfig curl font-noto font-noto-arabic font-noto-hebrew font-noto-cjk java-cacerts && \
apk upgrade && \
@@ -52,6 +49,7 @@ RUN apk add -U bash fontconfig curl font-noto font-noto-arabic font-noto-hebrew
mkdir -p /app/certs && \
curl https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem -o /app/certs/rds-combined-ca-bundle.pem && \
/opt/java/openjdk/bin/keytool -noprompt -import -trustcacerts -alias aws-rds -file /app/certs/rds-combined-ca-bundle.pem -keystore /etc/ssl/certs/java/cacerts -keypass changeit -storepass changeit && \
curl https://cacerts.digicert.com/DigiCertGlobalRootG2.crt.pem -o /app/certs/DigiCertGlobalRootG2.crt.pem && \
/opt/java/openjdk/bin/keytool -noprompt -import -trustcacerts -alias azure-cert -file /app/certs/DigiCertGlobalRootG2.crt.pem -keystore /etc/ssl/certs/java/cacerts -keypass changeit -storepass changeit && \
mkdir -p /plugins && chmod a+rwx /plugins

101
bb.edn
View File

@@ -260,7 +260,7 @@
{:doc "Generate XML configuration for Cursive's symbol resolution based on the current clj-kondo lint-as configuration."
:examples [["./bin/mage generate-cursive-resolve-as" "Generate resolve-as XML configuration"]
["./bin/mage generate-cursive-resolve-as > /Users/bgrabow/Library/Application\\ Support/JetBrains/IntelliJIdea2025.3/options/ClojureResolveSettings.xml"
"Generate resolve-as XML configuration and install it in your global editor configuration"]]
"Generate resolve-as XML configuration and install it in your global editor configuration"]]
:requires [[mage.cursive-resolve-as :as cursive-resolve-as]]
:task (task! (cursive-resolve-as/do-it))}
@@ -303,21 +303,17 @@
:task (task! (doctor/doctor!))}
ci-report
{:doc "Generate a CI failure report for a PR, branch, or commit"
:examples [["./bin/mage ci-report" "Check current branch (PR or branch CI)"]
{:doc "Generate a CI failure report for a PR"
:examples [["./bin/mage ci-report" "Check current branch, find its PR, and generate report"]
["./bin/mage ci-report 68292" "Generate report for PR #68292"]
["./bin/mage ci-report source-replacement" "Report for a branch by name"]
["./bin/mage ci-report abc123def" "Report for a specific commit SHA"]
["./bin/mage ci-report --detailed 68292" "Detailed report with full FAIL/ERROR blocks"]
["./bin/mage ci-report --detailed 68292" "Generate detailed report with full FAIL/ERROR blocks"]
["./bin/mage ci-report --detailed > ci_report.md"
"Save detailed report to markdown file and print progress."]]
:options [["-d" "--detailed" "Include full FAIL/ERROR blocks with expected/actual diffs"]]
:usage-fn (fn [_]
(str "Fetches CI check status and failed job logs from GitHub Actions.\n"
"Parses Trunk test reports to extract test failure details.\n\n"
"Accepts a PR number/URL, branch name, or commit SHA.\n"
"With no arguments, uses the current branch's PR if one exists,\n"
"otherwise falls back to the current branch's latest CI run.\n\n"
"With no arguments, looks up the PR for your current git branch.\n\n"
"Use --detailed to get full test failure output with expected/actual diffs.\n\n"
"Requires GitHub CLI (gh) to be installed and authenticated.\n"
"If you see 'gh: command not found' or auth errors, run: gh auth login"))
@@ -606,6 +602,87 @@
:requires [[mage.bgrabow.ci-test-repl :as ci-test-repl]]
:options [["-n" "--dry-run" "Print docker, env, and clojure commands without executing"]]
:task (task!
(let [wj (first (:arguments parsed))
[workflow job] (when wj (clojure.string/split wj (re-pattern "/") 2))]
(ci-test-repl/ci-test-repl {:workflow workflow :job job :dry-run? (:dry-run (:options parsed))})))}}}
(let [wj (first (:arguments parsed))
[workflow job] (when wj (clojure.string/split wj (re-pattern "/") 2))]
(ci-test-repl/ci-test-repl {:workflow workflow :job job :dry-run? (:dry-run (:options parsed))})))}
-fixbot-preflight
{:doc "Check prerequisites for the fixbot workflow"
:examples [["./bin/mage -fixbot-preflight" "Run all preflight checks"]]
:requires [[mage.fixbot.preflight :as preflight]]
:task (task! (preflight/preflight!))}
-fixbot-status
{:doc "Watch and display fixbot status"
:requires [[mage.fixbot.status :as status]]
:arg-schema [:? [:string {:name "file-path"}]]
:task (task! (status/run! parsed))}
fixbot-go
{:doc "Launch auto-fix workflow for a Linear issue"
:examples [["./bin/mage fixbot-go MB-12345 --app-db postgres --prompt-file /tmp/prompt.md --branch 'username/mb-12345-fix-thing'"
"Launch workmux session"]]
:requires [[mage.fixbot.go :as fixbot-go]]
:arg-schema [:tuple [:string {:name "issue-id"}]]
:options [[nil "--app-db DB" "App database" :default "postgres"]
[nil "--prompt-file FILE" "Path to agent prompt file"]
[nil "--branch BRANCH" "Branch name from Linear"]
[nil "--base BASE" "Base branch/commit to branch from"]]
:task (task! (fixbot-go/run! parsed))}
-fixbot-dev-env
{:doc "Stand up dev environment for auto-fix workflow"
:examples [["./bin/mage -fixbot-dev-env --app-db postgres" "Postgres app-db"]
["./bin/mage -fixbot-dev-env --down" "Tear down"]]
:requires [[mage.fixbot.dev-env :as fixbot-dev-env]]
:options [[nil "--app-db DB" "App database: postgres (default), mysql, h2" :default "postgres"]
[nil "--down" "Tear down containers"]
[nil "--status" "Show status"]
[nil "--slot SLOT" "Override port slot" :parse-fn Integer/parseInt]]
:task (task! (fixbot-dev-env/dev-env! parsed))}
-fixbot-fetch-issue
{:doc "Fetch a Linear issue by identifier"
:examples [["./bin/mage -fixbot-fetch-issue MB-12345" "Fetch and display issue"]]
:requires [[mage.fixbot.linear :as linear]]
:arg-schema [:tuple [:string {:name "issue-id"}]]
:task (task! (linear/print-issue! parsed))}
-fixbot-pause
{:doc "Pause a fixbot session (stop containers, close tmux, keep worktree)"
:examples [["./bin/mage -fixbot-pause mb-12345" "Pause session"]]
:requires [[mage.fixbot.session :as session]]
:arg-schema [:tuple [:string {:name "name-or-id"}]]
:task (task! (session/pause! parsed))}
-fixbot-resume
{:doc "Resume a paused fixbot session"
:examples [["./bin/mage -fixbot-resume mb-12345" "Resume session"]]
:requires [[mage.fixbot.session :as session]]
:arg-schema [:tuple [:string {:name "name-or-id"}]]
:task (task! (session/resume! parsed))}
-fixbot-list
{:doc "List fixbot sessions with status"
:examples [["./bin/mage -fixbot-list" "List all sessions"]]
:requires [[mage.fixbot.session :as session]]
:task (task! (session/list-sessions! parsed))}
-fixbot-quit
{:doc "Tear down and remove a fixbot worktree session"
:examples [["./bin/mage -fixbot-quit mb-12345" "Remove session and worktree"]]
:requires [[mage.fixbot.session :as session]]
:arg-schema [:tuple [:string {:name "name-or-id"}]]
:task (task! (session/quit! parsed))}
-fixbot-write-sandbox-settings
{:doc "Write container-compatible settings.local.json for sandbox"
:examples [["./bin/mage -fixbot-write-sandbox-settings" "Write sandbox settings"]]
:requires [[mage.fixbot.session :as session]]
:task (task! (session/write-sandbox-settings! parsed))}
-fixbot-dashboard
{:doc "Open fixbot TUI dashboard"
:examples [["./bin/mage -fixbot-dashboard" "Open dashboard"]]
:requires [[mage.fixbot.session :as session]]
:task (task! (session/dashboard!))}}}

View File

@@ -93,4 +93,4 @@ exec docker run -it --rm \
"${ENV_ARGS[@]}" \
--add-host=host.docker.internal:host-gateway \
"$IMAGE_NAME" \
--dangerously-skip-permissions
--dangerously-skip-permissions "$@"

View File

@@ -1,22 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----

View File

@@ -5,9 +5,6 @@ ENV FC_LANG=en-US LC_CTYPE=en_US.UTF-8
ARG GIT_COMMIT_SHA
ENV GIT_COMMIT_SHA=${GIT_COMMIT_SHA}
# copy cert before RUN so keytool can import it
COPY DigiCertGlobalRootG2.crt.pem /app/certs/DigiCertGlobalRootG2.crt.pem
# dependencies
RUN apk add -U bash fontconfig curl font-noto font-noto-arabic font-noto-hebrew font-noto-cjk font-noto-thai java-cacerts && \
apk upgrade && \
@@ -15,6 +12,7 @@ RUN apk add -U bash fontconfig curl font-noto font-noto-arabic font-noto-hebrew
mkdir -p /app/certs && \
curl https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem -o /app/certs/rds-combined-ca-bundle.pem && \
/opt/java/openjdk/bin/keytool -noprompt -import -trustcacerts -alias aws-rds -file /app/certs/rds-combined-ca-bundle.pem -keystore /etc/ssl/certs/java/cacerts -keypass changeit -storepass changeit && \
curl https://cacerts.digicert.com/DigiCertGlobalRootG2.crt.pem -o /app/certs/DigiCertGlobalRootG2.crt.pem && \
/opt/java/openjdk/bin/keytool -noprompt -import -trustcacerts -alias azure-cert -file /app/certs/DigiCertGlobalRootG2.crt.pem -keystore /etc/ssl/certs/java/cacerts -keypass changeit -storepass changeit && \
mkdir -p /plugins && chmod a+rwx /plugins

View File

@@ -5,15 +5,13 @@ ENV FC_LANG=en-US LC_CTYPE=en_US.UTF-8
ARG GIT_COMMIT_SHA
ENV GIT_COMMIT_SHA=${GIT_COMMIT_SHA}
# copy cert before RUN so it's available without a network fetch
COPY DigiCertGlobalRootG2.crt.pem /usr/local/share/ca-certificates/DigiCertGlobalRootG2.crt.pem
# Dependencies
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y ca-certificates ca-certificates-java fonts-noto && \
apt-get clean && \
curl https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem -o /usr/local/share/ca-certificates/rds-combined-ca-bundle.pem && \
curl https://cacerts.digicert.com/DigiCertGlobalRootG2.crt.pem -o /usr/local/share/ca-certificates/DigiCertGlobalRootG2.crt.pem && \
update-ca-certificates && \
mkdir -p /plugins && chmod a+rwx /plugins && \
keytool -list -cacerts

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