diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md index b2f479fa1..8cf50d149 100644 --- a/.agents/AGENTS.md +++ b/.agents/AGENTS.md @@ -240,3 +240,4 @@ Located in `.agents/skills/`. Reference for detailed patterns: | [kotlin-multiplatform](./skills/kotlin-multiplatform/SKILL.md) | KMP patterns, expect/actual | KMP modules | | [frontend-design](./skills/frontend-design/SKILL.md) | Create production-grade frontend UI with strong visual direction while avoiding generic AI patterns | Building or refining web components, pages, dashboards, or application UI | | [conventional-commits](./skills/conventional-commits/SKILL.md) | Conventional Commits specification | Creating commits, git messages | +| [github-actions](./skills/github-actions/SKILL.md) | GitHub Actions CI/CD best practices, security, optimization | `.github/workflows/*.yml`, CI/CD pipelines | diff --git a/.agents/skills/github-actions/SKILL.md b/.agents/skills/github-actions/SKILL.md new file mode 100644 index 000000000..b7fb5ad99 --- /dev/null +++ b/.agents/skills/github-actions/SKILL.md @@ -0,0 +1,225 @@ +--- +name: github-actions +description: > + GitHub Actions CI/CD best practices for workflow design, security hardening, and pipeline optimization. + Trigger: When creating, reviewing, or auditing GitHub Actions workflows (.github/workflows/*.{yml,yaml}), + fixing CI/CD issues, or hardening pipeline security. +license: Apache-2.0 +metadata: + author: "@dallay" + version: "1.0" +--- + +# GitHub Actions CI/CD Skill + +Expert guidance for designing, securing, and optimizing GitHub Actions CI/CD pipelines. + +## When to Use + +- Creating or modifying `.github/workflows/*.{yml,yaml}` files +- Auditing existing CI/CD pipelines for security, performance, or reliability issues +- Fixing workflow triggers, permissions, or caching problems +- Setting up deployment pipelines (staging, production, rollback) +- Integrating security scanning (SAST, SCA, secret scanning) into CI +- Optimizing workflow execution time (caching, matrix, parallelism) + +## Critical Patterns + +### Security (Non-Negotiable) + +- **Pin actions to full SHA**: Always use `uses: owner/action@<40-char-sha> # vX.Y.Z`. Tags + (`@v4`, `@main`) are mutable and vulnerable to supply chain attacks. Add version as comment for + readability. +- **Least-privilege permissions**: Set `permissions: {}` or `contents: read` at workflow level. + Override per-job only when writes are needed. Never leave permissions at default (broad write). +- **Secrets via `secrets.*` only**: Never hardcode sensitive data. Use environment-specific secrets + for deployment workflows with manual approval gates. +- **OIDC over static credentials**: For cloud auth (AWS, Azure, GCP), prefer OIDC short-lived + tokens over long-lived access keys stored as secrets. +- **Audit third-party actions**: Prefer `actions/` org. Review source for external actions. Enable + Dependabot for action version updates. +- **`pull_request_target` caution**: This trigger runs with write permissions against the base repo. + Never checkout untrusted PR code and execute it in this context. + +### Workflow Structure + +- **Descriptive names**: Workflow `name` and step `name` must be clear for log readability. +- **Concurrency control**: Use `concurrency` with `cancel-in-progress: true` to prevent duplicate + runs and waste. +- **Conditional execution**: Use `if` conditions for branch-specific, actor-specific, or + event-specific logic. +- **Reusable workflows**: Extract common patterns into `workflow_call` workflows (prefix with `_` + for internal ones, e.g., `_publish.yml` or `_publish.yaml`). +- **Timeout**: Set `timeout-minutes` on long-running jobs to prevent hung runners. + +### Performance + +- **Caching**: Use `actions/cache` (pinned SHA) with `hashFiles` keys for package managers and + build outputs. Use `restore-keys` for fallback. +- **Shallow clone**: Use `fetch-depth: 1` unless full history is needed (release tagging, blame). +- **Matrix parallelization**: Use `strategy.matrix` with `fail-fast: false` for comprehensive + multi-env testing. +- **Artifacts for inter-job data**: Use `actions/upload-artifact` / `actions/download-artifact` + (pinned SHAs) with appropriate `retention-days`. + +### Deployment + +- **Environment protection**: Use GitHub `environment` with required reviewers, branch restrictions, + and deployment URLs. +- **Rollback strategy**: Keep previous artifacts. Implement automated rollback on health check + failure. +- **Version validation**: Verify version consistency across manifests before publishing. +- **Idempotent publishes**: Check if version already exists before publishing to registries. + +## Decision Tables + +### When to Use `fetch-depth: 0` (Full History) + +| Use Case | `fetch-depth` | +|-----------------------------|---------------| +| Standard build/test | `1` | +| Release tagging/changelog | `0` | +| Commit message validation | `0` | +| CodeQL / deep analysis | `0` or omit | +| Dependency lockfile fix | `0` | + +### Choosing Deployment Strategy + +| Risk Tolerance | Strategy | Rollback Speed | +|----------------|-----------------|----------------| +| Low | Blue/Green | Instant | +| Medium | Canary | Fast | +| High | Rolling Update | Moderate | +| Feature flags | Dark Launch | Instant | + +### Permission Mapping + +| Workflow Need | Permission Required | +|----------------------------------|------------------------------| +| Read code only | `contents: read` | +| Push commits / create tags | `contents: write` | +| Comment on / update PRs | `pull-requests: write` | +| Upload SARIF security results | `security-events: write` | +| Deploy to GitHub Pages | `pages: write`, `id-token: write` | +| Publish to GitHub Packages | `packages: write` | +| Create/update issues | `issues: write` | +| Manage caches | `actions: write` | + +## Code Examples + +### Secure Workflow Skeleton + +```yaml +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + # ... build steps +``` + +### Effective Caching Pattern + +```yaml +- name: Cache dependencies + uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1 + with: + path: | + ~/.npm + ./node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- +``` + +### Environment-Protected Deployment + +```yaml +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: production + url: https://prod.example.com + permissions: + contents: read + id-token: write # For OIDC + steps: + - name: Configure cloud credentials (OIDC) + uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 + with: + role-to-assume: arn:aws:iam::123456789:role/deploy + aws-region: us-east-1 +``` + +## Audit Checklist + +When reviewing workflows, check: + +1. **All `uses:` pinned to full SHA** with version comment +2. **`permissions:` explicitly set** (workflow + job level) +3. **`concurrency:` configured** for non-trivial workflows +4. **`timeout-minutes:` set** on long-running jobs +5. **Secrets accessed via `secrets.*`** only, never in logs +6. **`fetch-depth: 1`** unless full history needed +7. **Caching configured** for package manager dependencies +8. **`if` conditions** for conditional steps (bot exclusions, branch filters) +9. **Environment protection** for deployment jobs +10. **No mutable action refs** (`@v4`, `@main`, `@latest`) + +## Troubleshooting Quick Reference + +| Symptom | Likely Cause | Fix | +|--------------------------------|--------------------------------------|-----------------------------------------------| +| Workflow not triggering | Wrong `on` trigger or path filter | Verify branch/path/event config | +| `Resource not accessible` | Missing `permissions` | Add required permission at job level | +| Cache miss every run | Dynamic cache key | Use `hashFiles` for stable keys | +| Slow checkout | `fetch-depth: 0` on large repo | Use `fetch-depth: 1` | +| Flaky tests in CI | Race conditions, env differences | Use explicit waits, `services` for deps | +| `pull_request_target` exploit | Running untrusted code with perms | Never checkout PR head in `_target` trigger | + +## Commands + +```bash +# Validate workflow YAML syntax locally +npx yaml-lint .github/workflows/*.{yml,yaml} + +# Mirror CodeRabbit's GitHub Actions validation locally with actionlint +# using the repository rule configuration in .github/actionlint.yml +actionlint -config-file .github/actionlint.yml .github/workflows/*.{yml,yaml} + +# List action versions and check for updates +gh api repos/actions/checkout/releases/latest --jq '.tag_name' + +# Resolve tag to immutable SHA (for pinning) +git ls-remote --tags https://github.com/actions/checkout.git | rg "refs/tags/v6.0.2(\^\{\})?$" | sort | tail -n 1 | awk '{print $1}' + +# Check workflow run status (use workflow name or either file extension) +gh run list --workflow="PR Checks" --limit=5 +``` + +## Resources + +- [GitHub Actions Security Hardening](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions) +- [GitHub Actions Reusable Workflows](https://docs.github.com/en/actions/sharing-automations/reusing-workflows) +- See [pinned-tag skill](../pinned-tag/SKILL.md) for SHA resolution workflow diff --git a/.github/workflows/_publish.yml b/.github/workflows/_publish.yml index 87e0c3a3e..817fdf3fa 100644 --- a/.github/workflows/_publish.yml +++ b/.github/workflows/_publish.yml @@ -183,12 +183,14 @@ jobs: if: ${{ inputs.release }} continue-on-error: true env: + SIGNING_KEY: ${{ secrets.SIGNING_IN_MEMORY_KEY }} + MAVEN_USER: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }} ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }} ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} run: | - if [ -z "${{ secrets.SIGNING_IN_MEMORY_KEY }}" ] || [ -z "${{ secrets.MAVEN_CENTRAL_USERNAME }}" ]; then + if [ -z "$SIGNING_KEY" ] || [ -z "$MAVEN_USER" ]; then echo "⚠️ Skipping Maven Central publish - required secrets not configured" exit 0 fi @@ -208,7 +210,7 @@ jobs: env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} run: | - if [ -z "${{ secrets.CARGO_REGISTRY_TOKEN }}" ]; then + if [ -z "$CARGO_REGISTRY_TOKEN" ]; then echo "⚠️ Skipping crates.io publish - CARGO_REGISTRY_TOKEN not configured" exit 0 fi @@ -322,7 +324,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: 📦 Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" @@ -398,7 +400,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: 📦 Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/auto-fix-lockfile.yml b/.github/workflows/auto-fix-lockfile.yml index 78cb42dea..7d299bc01 100755 --- a/.github/workflows/auto-fix-lockfile.yml +++ b/.github/workflows/auto-fix-lockfile.yml @@ -20,10 +20,21 @@ jobs: with: fetch-depth: 0 + - name: 📦 Setup pnpm + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 + with: + version: 10 + - name: 📦 Setup Node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: "24" + cache: "pnpm" + cache-dependency-path: | + pnpm-lock.yaml + clients/web/pnpm-lock.yaml + clients/web/apps/docs/pnpm-lock.yaml + clients/web/apps/marketing/pnpm-lock.yaml - name: ☕ Setup Java uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 diff --git a/.github/workflows/cleanup-cache.yml b/.github/workflows/cleanup-cache.yml index 455839ed0..0e8e637d8 100644 --- a/.github/workflows/cleanup-cache.yml +++ b/.github/workflows/cleanup-cache.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json name: Cleanup caches on: @@ -10,5 +11,5 @@ permissions: jobs: cleanup: - uses: dallay/common-actions/.github/workflows/cleanup-cache.yml@main + uses: dallay/common-actions/.github/workflows/cleanup-cache.yml@e77aee209c07cd031e1fc6a3f275df774b4685a2 # v1.1.0 secrets: inherit diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 967a629d0..a15b3f7fe 100755 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,10 +37,21 @@ jobs: - name: ✈ Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: 📦 Setup pnpm + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 + with: + version: 10 + - name: 📦 Setup Node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: "24" + cache: "pnpm" + cache-dependency-path: | + pnpm-lock.yaml + clients/web/pnpm-lock.yaml + clients/web/apps/docs/pnpm-lock.yaml + clients/web/apps/marketing/pnpm-lock.yaml - name: ☕ Setup Java uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 diff --git a/.github/workflows/contributor-report.yml b/.github/workflows/contributor-report.yml index 27d9fbc26..5dc4e3169 100644 --- a/.github/workflows/contributor-report.yml +++ b/.github/workflows/contributor-report.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json name: Contributor Report on: @@ -10,5 +11,5 @@ permissions: jobs: report: - uses: dallay/common-actions/.github/workflows/contributor-report.yml@main + uses: dallay/common-actions/.github/workflows/contributor-report.yml@e77aee209c07cd031e1fc6a3f275df774b4685a2 # v1.1.0 secrets: inherit diff --git a/.github/workflows/core-check.yml b/.github/workflows/core-check.yml index 15ff2984a..9cdd1f540 100644 --- a/.github/workflows/core-check.yml +++ b/.github/workflows/core-check.yml @@ -41,7 +41,7 @@ jobs: - name: ✈ Checkout default branch uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - fetch-depth: 0 + fetch-depth: 1 - name: ☕ Setup Java uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 diff --git a/.github/workflows/detekt.yml b/.github/workflows/detekt.yml index 3183adc13..cbd7c37ed 100644 --- a/.github/workflows/detekt.yml +++ b/.github/workflows/detekt.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support @@ -44,6 +45,7 @@ jobs: # Required for upload-sarif to push security events permissions: + contents: read security-events: write # Steps represent a sequence of tasks that will be executed as part of the job @@ -122,7 +124,7 @@ jobs: )" > "${{ github.workspace }}/detekt.sarif.json" # Uploads results to GitHub repository using the upload-sarif action - - uses: github/codeql-action/upload-sarif@v4 + - uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10 with: # Path to SARIF file relative to the root of the repository sarif_file: ${{ github.workspace }}/detekt.sarif.json diff --git a/.github/workflows/fix-renovate.yml b/.github/workflows/fix-renovate.yml index 6fcd38a21..6d4b68619 100755 --- a/.github/workflows/fix-renovate.yml +++ b/.github/workflows/fix-renovate.yml @@ -85,10 +85,21 @@ jobs: exit 1 fi + - name: 📦 Setup pnpm + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 + with: + version: 10 + - name: 📦 Setup Node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: "24" + cache: "pnpm" + cache-dependency-path: | + pnpm-lock.yaml + clients/web/pnpm-lock.yaml + clients/web/apps/docs/pnpm-lock.yaml + clients/web/apps/marketing/pnpm-lock.yaml - name: ☕ Setup Java uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml index 6dcbf9d81..7762d3d9b 100644 --- a/.github/workflows/greetings.yml +++ b/.github/workflows/greetings.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json name: Greetings on: [pull_request_target, issues] @@ -8,5 +9,5 @@ permissions: jobs: greet: - uses: dallay/common-actions/.github/workflows/greetings.yml@main + uses: dallay/common-actions/.github/workflows/greetings.yml@e77aee209c07cd031e1fc6a3f275df774b4685a2 # v1.1.0 secrets: inherit diff --git a/.github/workflows/pull-request-check-build-logic.yml b/.github/workflows/pull-request-check-build-logic.yml index e1964acc7..f852ba2fd 100755 --- a/.github/workflows/pull-request-check-build-logic.yml +++ b/.github/workflows/pull-request-check-build-logic.yml @@ -31,6 +31,8 @@ jobs: pr-checks-build-logic: runs-on: ubuntu-latest timeout-minutes: 60 + permissions: + contents: read steps: - name: ✈ Checkout default branch diff --git a/.github/workflows/pull-request-check.yml b/.github/workflows/pull-request-check.yml index 2cc95baa0..bc4fc8830 100755 --- a/.github/workflows/pull-request-check.yml +++ b/.github/workflows/pull-request-check.yml @@ -25,6 +25,8 @@ jobs: pr-checks: runs-on: ubuntu-latest timeout-minutes: 60 + permissions: + contents: read steps: - name: ✈ Checkout default branch @@ -48,10 +50,21 @@ jobs: # git fetch origin ${{ github.base_ref }}:target-branch # git merge --no-ff target-branch + - name: 📦 Setup pnpm + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 + with: + version: 10 + - name: 📦 Setup Node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: "24" + cache: "pnpm" + cache-dependency-path: | + pnpm-lock.yaml + clients/web/pnpm-lock.yaml + clients/web/apps/docs/pnpm-lock.yaml + clients/web/apps/marketing/pnpm-lock.yaml - name: ☕ Setup Java uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 diff --git a/.github/workflows/pull-request-limit.yml b/.github/workflows/pull-request-limit.yml index ba0b148cc..265aacce7 100644 --- a/.github/workflows/pull-request-limit.yml +++ b/.github/workflows/pull-request-limit.yml @@ -9,6 +9,10 @@ on: jobs: pr-limit: + permissions: + contents: read + issues: write + pull-requests: write if: > github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name == github.repository && diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index f36dfe721..559d07ee2 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 🤖 Run release-please - uses: googleapis/release-please-action@c3fc4de07084f75a2b61a5b933069bda6edf3d5c # v4 + uses: googleapis/release-please-action@c3fc4de07084f75a2b61a5b933069bda6edf3d5c # v4.4.0 with: token: ${{ secrets.RELEASE_PLEASE_TOKEN }} config-file: release-please-config.json diff --git a/.github/workflows/security-dependencies.yml b/.github/workflows/security-dependencies.yml index 8fd1c21e1..a2ecac2a9 100644 --- a/.github/workflows/security-dependencies.yml +++ b/.github/workflows/security-dependencies.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: 🦀 Setup Rust toolchain - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 + uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # master 2026-03-04 with: toolchain: stable @@ -45,7 +45,7 @@ jobs: - name: ☕ Setup Java uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: - java-version: "21" + java-version: "25" distribution: "corretto" - name: 🐘 Submit Dependency Graph diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml index 18d59d61a..f3b73e65e 100644 --- a/.github/workflows/semantic-pull-request.yml +++ b/.github/workflows/semantic-pull-request.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json name: Lint PR title on: @@ -9,5 +10,5 @@ permissions: jobs: lint: - uses: dallay/common-actions/.github/workflows/semantic-pr.yml@main + uses: dallay/common-actions/.github/workflows/semantic-pr.yml@e77aee209c07cd031e1fc6a3f275df774b4685a2 # v1.1.0 secrets: inherit diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 9f0a3c187..b3e5f5b46 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json name: Stale on: @@ -10,5 +11,5 @@ permissions: jobs: stale: - uses: dallay/common-actions/.github/workflows/stale.yml@main + uses: dallay/common-actions/.github/workflows/stale.yml@e77aee209c07cd031e1fc6a3f275df774b4685a2 # v1.1.0 secrets: inherit diff --git a/clients/agent-runtime/src/tools/git_operations.rs b/clients/agent-runtime/src/tools/git_operations.rs index 7e4716bb4..acabb524b 100755 --- a/clients/agent-runtime/src/tools/git_operations.rs +++ b/clients/agent-runtime/src/tools/git_operations.rs @@ -670,6 +670,22 @@ mod tests { use crate::security::SecurityPolicy; use tempfile::TempDir; + fn init_temp_git_repo(dir: &std::path::Path) { + let output = std::process::Command::new("git") + .args(["init"]) + .current_dir(dir) + .env_remove("GIT_DIR") + .env_remove("GIT_WORK_TREE") + .output() + .unwrap(); + + assert!( + output.status.success(), + "git init failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + fn test_tool(dir: &std::path::Path) -> GitOperationsTool { let security = Arc::new(SecurityPolicy { autonomy: AutonomyLevel::Supervised, @@ -791,12 +807,7 @@ mod tests { #[tokio::test] async fn blocks_readonly_mode_for_write_ops() { let tmp = TempDir::new().unwrap(); - // Initialize a git repository - std::process::Command::new("git") - .args(["init"]) - .current_dir(tmp.path()) - .output() - .unwrap(); + init_temp_git_repo(tmp.path()); let security = Arc::new(SecurityPolicy { autonomy: AutonomyLevel::ReadOnly, @@ -820,12 +831,7 @@ mod tests { #[tokio::test] async fn allows_branch_listing_in_readonly_mode() { let tmp = TempDir::new().unwrap(); - // Initialize a git repository so the command can succeed - std::process::Command::new("git") - .args(["init"]) - .current_dir(tmp.path()) - .output() - .unwrap(); + init_temp_git_repo(tmp.path()); let security = Arc::new(SecurityPolicy { autonomy: AutonomyLevel::ReadOnly, @@ -883,12 +889,7 @@ mod tests { #[tokio::test] async fn rejects_unknown_operation() { let tmp = TempDir::new().unwrap(); - // Initialize a git repository - std::process::Command::new("git") - .args(["init"]) - .current_dir(tmp.path()) - .output() - .unwrap(); + init_temp_git_repo(tmp.path()); let tool = test_tool(tmp.path());