Skip to content

fix: scope database health check to current repo to enable concurrent sessions #1116

fix: scope database health check to current repo to enable concurrent sessions

fix: scope database health check to current repo to enable concurrent sessions #1116

Workflow file for this run

name: PR Checks
on:
pull_request:
branches: [main]
env:
COVERAGE_THRESHOLD: 51.0
concurrency:
group: pr-checks-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
statuses: write
jobs:
validate-pr-title:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Validate PR title
uses: amannn/action-semantic-pull-request@v6
with:
# NOTE: Keep these types in sync with cz_conventional_commits in pyproject.toml
# Standard Conventional Commits types as used by commitizen
types: |
feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert
requireScope: false
validateSingleCommit: false
ignoreLabels: |
skip-validation
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Linting and type checking (runs in parallel with tests)
lint-and-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ''
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install dependencies
run: uv sync --all-extras
- name: Run ruff
run: uv run ruff check src/
- name: Run ruff format check
run: uv run ruff format --check src/
- name: Run mypy
run: uv run mypy src/
# Test Group 1: Config, conversation, history, and agents (medium load)
test-group-1:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ''
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install dependencies
run: uv sync --all-extras
- name: Run tests - Config, History & Agents
run: |
uv run pytest \
test/unit/test_config*.py \
test/unit/test_conversation*.py \
test/unit/test_history*.py \
test/unit/agents/ \
--cov=src --cov-report= -q
# Note: test_config*.py includes test_config_manager.py and test_config_migrations.py
- name: Upload coverage data
uses: actions/upload-artifact@v7
with:
name: coverage-group-1
path: .coverage
include-hidden-files: true
# Test Group 2: Codebase, LLM proxy, Web (heavy load - Kuzu database)
test-group-2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ''
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install dependencies
run: uv sync --all-extras
- name: Run tests - Codebase, LLM Proxy & Web
run: |
uv run pytest \
test/unit/codebase/ \
test/unit/llm_proxy/ \
test/unit/shotgun_web/ \
test/unit/test_web_search.py \
--cov=src --cov-report= -q
- name: Upload coverage data
uses: actions/upload-artifact@v7
with:
name: coverage-group-2
path: .coverage
include-hidden-files: true
# Test Group 3: CLI, TUI, exceptions & utilities (light load)
test-group-3:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ''
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install dependencies
run: uv sync --all-extras
- name: Run tests - CLI, TUI & Utilities
run: |
uv run pytest \
test/unit/cli/ \
test/unit/tui/ \
test/unit/exceptions/ \
test/unit/test_common.py \
test/unit/test_file_management.py \
test/unit/test_file_system_utils.py \
test/unit/test_logging_config.py \
test/unit/test_marketing.py \
test/unit/test_posthog_telemetry.py \
test/unit/test_provider.py \
test/unit/test_settings.py \
test/unit/test_streaming_detection.py \
test/unit/test_update_checker.py \
test/unit/test_agent_manager_filtering.py \
--cov=src --cov-report= -q
- name: Upload coverage data
uses: actions/upload-artifact@v7
with:
name: coverage-group-3
path: .coverage
include-hidden-files: true
# Combine coverage from all test groups
coverage-report:
runs-on: ubuntu-latest
needs: [test-group-1, test-group-2, test-group-3]
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ''
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install dependencies
run: uv sync --all-extras
- name: Download coverage artifacts
uses: actions/download-artifact@v8
with:
pattern: coverage-group-*
merge-multiple: false
- name: Combine coverage data
run: |
# Move coverage files with unique names
mv coverage-group-1/.coverage .coverage.group1
mv coverage-group-2/.coverage .coverage.group2
mv coverage-group-3/.coverage .coverage.group3
# Combine all coverage data
uv run coverage combine
- name: Generate coverage reports
run: |
uv run coverage report --show-missing
uv run coverage xml
- name: Check core coverage (${{ env.COVERAGE_THRESHOLD }}% minimum, excluding CLI/TUI)
run: |
# Use combined coverage data from all test groups
# excluding CLI and TUI directories
echo "Checking combined coverage for core functionality (excluding CLI/TUI)..."
# Run coverage report with specific omit patterns for CLI/TUI and extract percentage
COVERAGE_LINE=$(uv run coverage report --omit="*/cli/*,*/tui/*" | grep "^TOTAL" | awk '{print $NF}' | sed 's/%//')
COVERAGE_OUTPUT=${COVERAGE_LINE:-0}
echo "Core coverage: ${COVERAGE_OUTPUT}%"
echo ""
echo "Current coverage by file (excluding CLI/TUI):"
uv run coverage report --omit="*/cli/*,*/tui/*" --show-missing
echo ""
# Check if coverage meets threshold using bc for decimal comparison
if (( $(echo "${COVERAGE_OUTPUT} < ${COVERAGE_THRESHOLD}" | bc -l) )); then
echo "❌ Coverage check failed: ${COVERAGE_OUTPUT}% < ${COVERAGE_THRESHOLD}%"
echo ""
echo "Please add tests to improve coverage of core functionality"
echo "Focus on: agents/, config/, tools/, models/, history/ directories"
echo ""
echo "Low coverage files that need attention:"
uv run coverage report --omit="*/cli/*,*/tui/*" --show-missing | grep -E "^\w" | awk -v threshold="${COVERAGE_THRESHOLD}" '$NF ~ /%/ && $NF+0 < threshold {print " - " $1 ": " $NF}'
exit 1
else
echo "✅ Coverage check passed: ${COVERAGE_OUTPUT}% >= ${COVERAGE_THRESHOLD}%"
fi
test-cli-basics:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Create virtual environment
run: uv venv
- name: Install dependencies
run: |
source .venv/bin/activate
uv sync --all-extras
- name: Test shotgun version
run: |
source .venv/bin/activate
shotgun --version
- name: Test shotgun help
run: |
source .venv/bin/activate
shotgun --help
check-permissions:
runs-on: ubuntu-latest
outputs:
is-maintainer: ${{ steps.check.outputs.is-maintainer }}
steps:
- name: Check if user is maintainer
id: check
uses: actions/github-script@v8
with:
script: |
try {
const actor = context.actor;
const { owner, repo } = context.repo;
console.log(`Checking permissions for user: ${actor}`);
// Get the user's permission level for this repository
const { data: permissionData } = await github.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username: actor,
});
const permission = permissionData.permission;
console.log(`User ${actor} has permission level: ${permission}`);
// Maintainers have 'write', 'maintain', or 'admin' permissions
const isMaintainer = ['write', 'maintain', 'admin'].includes(permission);
console.log(`Is maintainer: ${isMaintainer}`);
core.setOutput('is-maintainer', isMaintainer.toString());
return isMaintainer;
} catch (error) {
console.error('Error checking permissions:', error);
// On error, default to false (non-maintainer)
core.setOutput('is-maintainer', 'false');
return false;
}
test-cli-run:
runs-on: ubuntu-latest
needs: check-permissions
# Only run for maintainers who have access to API keys
if: needs.check-permissions.outputs.is-maintainer == 'true'
timeout-minutes: 5
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Create virtual environment
run: uv venv
- name: Install dependencies
run: |
source .venv/bin/activate
uv sync --all-extras
- name: Test shotgun run command
env:
SHOTGUN_LOG_LEVEL: DEBUG
run: |
source .venv/bin/activate
shotgun config set openai --api-key $OPENAI_API_KEY
shotgun run -n "What is 2 + 2? Reply with just the number."
secret-scanning:
name: Scan for secrets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # Full history for comprehensive scan
- name: Pull trufflehog Docker image
run: docker pull ghcr.io/trufflesecurity/trufflehog:latest
- name: Run trufflehog scan (pass 1 - all files except uv.lock)
run: docker run --rm -v "$PWD:/repo" ghcr.io/trufflesecurity/trufflehog:latest git file:///repo --fail --no-update --exclude-globs=uv.lock
- name: Run trufflehog scan (pass 2 - uv.lock only, exclude SentryToken)
run: docker run --rm -v "$PWD:/repo" ghcr.io/trufflesecurity/trufflehog:latest git file:///repo --fail --no-update --include-paths=/repo/.github/trufflehog-include-lockfile.txt --exclude-detectors=SentryToken
docker-build-test:
name: Docker build test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build Docker image
uses: docker/build-push-action@v7
with:
context: .
push: false
tags: shotgun:pr-${{ github.event.pull_request.number }}
cache-from: type=gha
cache-to: type=gha,mode=max
notify-slack-on-secret-found:
runs-on: ubuntu-latest
needs: [secret-scanning]
if: failure()
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 2
- name: Get PR and commit information
id: pr_info
run: |
# Get PR information
PR_NUMBER="${{ github.event.pull_request.number }}"
PR_TITLE="${{ github.event.pull_request.title }}"
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
PR_URL="${{ github.event.pull_request.html_url }}"
# Get commit information
COMMIT_SHA=$(git rev-parse HEAD)
COMMIT_SHORT=$(git rev-parse --short HEAD)
COMMIT_MESSAGE=$(git log -1 --pretty=format:"%s")
# Store outputs
echo "pr_number=${PR_NUMBER}" >> $GITHUB_OUTPUT
echo "pr_title=${PR_TITLE}" >> $GITHUB_OUTPUT
echo "pr_author=${PR_AUTHOR}" >> $GITHUB_OUTPUT
echo "pr_url=${PR_URL}" >> $GITHUB_OUTPUT
echo "commit_sha=${COMMIT_SHA}" >> $GITHUB_OUTPUT
echo "commit_short=${COMMIT_SHORT}" >> $GITHUB_OUTPUT
echo "commit_message=${COMMIT_MESSAGE}" >> $GITHUB_OUTPUT
- name: Send Slack notification
env:
SLACK_WEBHOOK_URL: ${{ secrets.SHOTGUN_ALPHA_WEBHOOK }}
run: |
# Prepare variables
PR_NUMBER="${{ steps.pr_info.outputs.pr_number }}"
PR_TITLE="${{ steps.pr_info.outputs.pr_title }}"
PR_AUTHOR="${{ steps.pr_info.outputs.pr_author }}"
PR_URL="${{ steps.pr_info.outputs.pr_url }}"
COMMIT_SHORT="${{ steps.pr_info.outputs.commit_short }}"
COMMIT_MESSAGE="${{ steps.pr_info.outputs.commit_message }}"
WORKFLOW_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
REPO_URL="${{ github.server_url }}/${{ github.repository }}"
COMMIT_URL="${REPO_URL}/commit/${{ steps.pr_info.outputs.commit_sha }}"
# Create Slack payload
cat <<EOF > /tmp/slack_payload.json
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "🚨 SECRET DETECTED IN PR",
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Trufflehog detected secrets or API keys in a pull request!*"
}
},
{
"type": "divider"
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*PR:*\n<${PR_URL}|#${PR_NUMBER}: ${PR_TITLE}>"
},
{
"type": "mrkdwn",
"text": "*Author:*\n@${PR_AUTHOR}"
},
{
"type": "mrkdwn",
"text": "*Commit:*\n<${COMMIT_URL}|${COMMIT_SHORT}>"
},
{
"type": "mrkdwn",
"text": "*Workflow:*\n<${WORKFLOW_URL}|View Details>"
}
]
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Action Required:*\n• Review the workflow logs to identify the detected secret\n• Rotate any exposed credentials immediately\n• Remove secrets from the commit history\n• Use environment variables or secret management tools"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "🔍 Detected by trufflehog secret scanner"
}
]
}
]
}
EOF
# Send to Slack
curl -X POST -H 'Content-type: application/json' \
--data @/tmp/slack_payload.json \
"$SLACK_WEBHOOK_URL"
all-checks-passed:
runs-on: ubuntu-latest
needs: [validate-pr-title, lint-and-typecheck, test-group-1, test-group-2, test-group-3, coverage-report, test-cli-basics, check-permissions, test-cli-run, secret-scanning, docker-build-test]
if: always()
timeout-minutes: 1
steps:
- name: Check job results
run: |
echo "validate-pr-title: ${{ needs.validate-pr-title.result }}"
echo "lint-and-typecheck: ${{ needs.lint-and-typecheck.result }}"
echo "test-group-1: ${{ needs.test-group-1.result }}"
echo "test-group-2: ${{ needs.test-group-2.result }}"
echo "test-group-3: ${{ needs.test-group-3.result }}"
echo "coverage-report: ${{ needs.coverage-report.result }}"
echo "test-cli-basics: ${{ needs.test-cli-basics.result }}"
echo "check-permissions: ${{ needs.check-permissions.result }}"
echo "test-cli-run: ${{ needs.test-cli-run.result }}"
echo "secret-scanning: ${{ needs.secret-scanning.result }}"
echo "docker-build-test: ${{ needs.docker-build-test.result }}"
# Required jobs that must always succeed
if [[ "${{ needs.validate-pr-title.result }}" != "success" || \
"${{ needs.lint-and-typecheck.result }}" != "success" || \
"${{ needs.test-group-1.result }}" != "success" || \
"${{ needs.test-group-2.result }}" != "success" || \
"${{ needs.test-group-3.result }}" != "success" || \
"${{ needs.coverage-report.result }}" != "success" || \
"${{ needs.test-cli-basics.result }}" != "success" || \
"${{ needs.check-permissions.result }}" != "success" || \
"${{ needs.secret-scanning.result }}" != "success" || \
"${{ needs.docker-build-test.result }}" != "success" ]]; then
echo "❌ One or more required checks failed or were cancelled"
exit 1
fi
# test-cli-run can be skipped for non-maintainers, but must succeed for maintainers
if [[ "${{ needs.test-cli-run.result }}" == "failure" || \
"${{ needs.test-cli-run.result }}" == "cancelled" ]]; then
echo "❌ CLI run test failed or was cancelled"
exit 1
fi
# If we get here, either all required checks passed, or test-cli-run was skipped
if [[ "${{ needs.test-cli-run.result }}" == "skipped" ]]; then
echo "⏭️ CLI run test skipped (non-maintainer PR)"
fi
echo "✅ All required checks have passed successfully!"