End-to-end bug fix workflow for the Exposed project. Accepts a GitHub issue (#NUMBER), YouTrack issue (EXPOSED-NUMBER), or YouTrack URL (https://youtrack.jetbrains.com/issue/EXPOSED-NUMBER). Fetches the issue, creates a failing reproducer test, commits on a new branch, implements the fix, validates, and opens a PR. Use this skill whenever the user wants to fix a bug from an issue tracker, mentions a EXPOSED issue number, or references a GitHub issue to fix.
Automates the full bug-fix lifecycle for the Exposed project: understand issue, reproduce, fix, validate, and open a PR.
The user provides one of:
#123 — GitHub issue in jetbrains/exposedEXPOSED-1234 — YouTrack issue IDhttps://youtrack.jetbrains.com/issue/EXPOSED-5678 or https://youtrack.jetbrains.com/issue/EXPOSED-9012/some-slug — YouTrack URLParse the input to determine the source:
gh issue view NUMBER --repo jetbrains/exposedEXPOSED-XXXX): Fetch via the YouTrack MCP (see below)EXPOSED-XXXX ID from the URL, then fetch via the YouTrack MCP (see below)Use the YouTrack MCP tools to fetch issue details. Call mcp__youtrack__get_issue with the issue ID (e.g., EXPOSED-1234).
If the YouTrack MCP server is not configured (tool calls fail), instruct the user to set it up:
claude mcp add --header "Authorization: Bearer <token>" --transport http youtrack https://youtrack.jetbrains.com/mcp
The permanent token can be created in JetBrains Hub account security settings (linked from YouTrack profile).
From the issue, extract:
EXPOSED-1234 or #123)For YouTrack issues only, update the issue status to reflect that work is starting:
mcp__youtrack__get_current_user to get the current user's login.mcp__youtrack__change_issue_assignee to assign the issue to the current user.mcp__youtrack__update_issue with customFields: {"State": "In Progress"} to mark work as started.If any of these calls fail because the YouTrack MCP is not configured, inform the user how to set it up (see "Fetching YouTrack Issues" section above) and continue with the rest of the workflow — issue tracking updates are not blocking.
Skip this step for GitHub-only issues.
Save the issue as a json file under issues directory.
Github issues should be saved in the /issues/github/ directory. The json file of the issue should be named as the issue number github-<id>.json
Youtrack issues should be saved in the /issues/youtrack/ directory. The json file of the issue should be named as the issue number youtrack-<id>.json
Before creating a branch, understand the affected area:
Use the Explore agent or direct file reads to understand:
Determine the base branch:
main branch for creating new branch that will be used for the PR with the fixgit checkout <base-branch> && git pull && git checkout -b claude/<issue-id>-<short-description>
Branch naming rules:
claude/EXPOSED-1234-short-descriptionclaude/123-short-descriptionWrite a test that demonstrates the bug as described in the issue. The goal is:
`requestWithEmptyBodyDoesntCausesNPE`)
/* EXPOSED-9352 request with empty body causes NPE */)Place the test appropriately:
exposed-tests (JDBC) and exposed-r2dbc-tests (R2DBC)
exposed-java-time, exposed-jodatime, exposed-kotlin-datetime for date/time issuesexposed-json for JSON column type issuesexposed-crypt for encrypted column issuesexposed-money for monetary amount issuesexposed-migration-jdbc, exposed-migration-r2dbc for migration issuesexposed-spring-boot-starter, spring-transaction for Spring integration issuesexcludeSettings parameter in test helper functionsAfter writing the test, run it to confirm it fails:
./gradlew gradle :exposed-tests:test --tests "fully.qualified.TestClassName.methodName"
./gradlew gradle :exposed-r2dbc-tests:test --tests "fully.qualified.TestClassName.methodName"
Tests for specific databases could be run in isolation. For example for the H2 it will be the following commands:
./gradlew gradle :exposed-tests:test_h2_v2 --tests "fully.qualified.TestClassName.methodName"
./gradlew gradle :exposed-r2dbc-tests:test_h2_v2 --tests "fully.qualified.TestClassName.methodName"
You must verify that the tests are compiled without errors. The tests should fail according to the test assertions only.
If the test passes (bug is already fixed or test doesn't reproduce correctly):
Stage and commit the failing test. All the commit message should follow the Conventional Commits format:
git add <test-file>
git commit -m "fix: <ISSUE-ID> Add failing test for <short bug description>
Co-Authored-By: Claude Opus 4.6 <[email protected]>"
For GitHub issues, use #NUMBER in the commit message. For YouTrack, use EXPOSED-XXXX.
Analyze the bug based on what you learned from the issue and the reproducer test:
Present your analysis and plan to the user for review. Include:
Wait for user approval before implementing.
If user provides additional context, concerns, or feedback, take it into account, and repeat Step 6 with the new input
After receiving user approval, implement the planned fix. Keep changes minimal and focused:
Run the reproducer test to confirm it now passes. Use the same test commands from Step 4:
# For JDBC tests
./gradlew :exposed-tests:test_h2_v2 --tests "fully.qualified.TestClassName.methodName"
# For R2DBC tests
./gradlew :exposed-r2dbc-tests:test_h2_v2 --tests "fully.qualified.TestClassName.methodName"
After confirming the reproducer passes, optionally run the broader test suite for the affected module:
# Run all H2 tests for the module
./gradlew :exposed-tests:test_h2_v2
./gradlew :exposed-r2dbc-tests:test_h2_v2
# Or test against specific database if the fix is database-specific
./gradlew :exposed-tests:test_postgres
./gradlew :exposed-r2dbc-tests:test_postgres
Refer to Step 4 for the full list of available test commands and database-specific test tasks.
If any tests fail, investigate and fix. Do not skip or disable tests.
Exposed uses Detekt for code style validation. Run the linter to check for any issues:
./gradlew detekt
This validates code style across all modules according to the rules defined in detekt/detekt-config.yml.
If Detekt reports any issues, fix them before proceeding. The build requires zero issues (max issues: 0).
If the fix changed any public or protected API (new methods, changed signatures, etc.), update the API documentation:
./gradlew apiDump
This command updates the Dokka API documentation files to reflect the public API changes.
Stage any updated API files (.api files) along with the fix:
git add <api-files>
If no public API changed, skip this step.
If the PR introduces a new feature or changes existing public API behavior, update the documentation website to reflect these changes.
Update documentation when:
Skip this step when:
The documentation website is located in the documentation-website directory:
documentation-website/Writerside/topics/ - XML files for each documentation pageIdentify the relevant topic:
Update the content:
Test locally (if possible):
Prefer modifying existing documentation files over creating new ones. Ask for user approval before creating new files in the documentation website.
Exposed uses Conventional Commits for commit messages. If the fix
has no breaking changes the prefix is fix:. If it introduces a breaking change, use fix!:.
Be aware that the commit message will be validated on CI by the following regex:
"^(build|chore|ci|deprecate|docs|feat|fix|perf|refactor|revert|style|test)(!)?(\([^\)]*\))?:\s?(EXPOSED-[0-9]+\s?)?.+$"
(it's defined in .github/workflows/commit-message-validation.yml file)
Stage and commit the fix:
git add <changed-files>
git commit -m "fix: <ISSUE-ID> <Imperative description of the fix>
Co-Authored-By: Claude Opus 4.6 <[email protected]>"
Push the branch and create a PR:
git push -u origin claude/<issue-id>-<short-description>
Create the PR targeting the base branch chosen in Step 3. The title should follow the Conventional Commits format and the the commit message should have the following structure:
gh pr create --title "fix: <ISSUE-ID> <Short fix description>" --body "$(cat <<'EOF'
#### Description
**Summary of the change**: Provide a concise summary of this PR. Describe the changes made in a single sentence or short paragraph.
**Detailed description**:
- **Why**: Explain the reasons behind the changes. Why were they necessary?
- **What**: Detail what changes have been made in the PR.
- **How**: Describe how the changes were implemented, including any key aspects of the code modified or new features added.
---
#### Type of Change
Please mark the relevant options with an "X":
- [ ] Bug fix
- [ ] New feature
- [ ] Documentation update
Updates/remove existing public API methods:
- [ ] Is breaking change
Affected databases:
- [ ] MariaDB
- [ ] Mysql5
- [ ] Mysql8
- [ ] Oracle
- [ ] Postgres
- [ ] SqlServer
- [ ] H2
- [ ] SQLite
#### Checklist
- [ ] Unit tests are in place
- [ ] The build is green (including the Detekt check)
- [ ] All public methods affected by my PR has up to date API docs
- [ ] Documentation for my change is up to date
---
#### Related Issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
The Closes line auto-closes the issue when the PR is merged for GitHub issues only:
Closes #NUMBEREXPOSED-XXXX as a plain cross-reference (GitHub will not close YouTrack tickets automatically)Report the PR URL to the user when done.
After the PR is created, for YouTrack issues only, update the issue state:
Call mcp__youtrack__update_issue with customFields: {"State": "Ready for Review"} to signal the fix is ready for code review.
If the YT MCP call fails, skip silently — the status update is not blocking.
Assess whether the fix changes behavior that users rely on or that is described in the Exposed documentation. A documentation update is needed when:
If none of the above apply (e.g., an internal-only fix, a crash fix with no API change), skip this step.
When documentation is needed, update the documentation in documentation-website directory.