crewAIInc/crewAI

13 workflows · maturity 67% · 7 patterns · GitHub ↗

Security 38.71/100

Practices

✓ Matrix✓ Permissions✓ Security scan○ AI review✓ Cache○ Concurrency○ Reusable workflows

Detected patterns

Security dimensions

permissions
15.4
security scan
8.3
supply chain
0
secret handling
15
harden runner
0

Tools: github/codeql-action/analyze, github/codeql-action/init

Workflows (13)

build-uv-cache matrix perms .github/workflows/build-uv-cache.yml
Triggers
push, schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
build-cache
Matrix
python-version→ 3.10, 3.11, 3.12, 3.13
Actions
astral-sh/setup-uv
Commands
  • echo "Building global UV cache for Python ${{ matrix.python-version }}..." uv sync --all-groups --all-extras --no-install-project echo "Cache populated successfully"
View raw YAML
name: Build uv cache

on:
  push:
    branches:
      - main
    paths:
      - "uv.lock"
      - "pyproject.toml"
  schedule:
    - cron: "0 0 */5 * *"  # Run every 5 days at midnight UTC to prevent cache expiration
  workflow_dispatch:

permissions:
  contents: read

jobs:
  build-cache:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12", "3.13"]

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          version: "0.8.4"
          python-version: ${{ matrix.python-version }}
          enable-cache: false

      - name: Install dependencies and populate cache
        run: |
          echo "Building global UV cache for Python ${{ matrix.python-version }}..."
          uv sync --all-groups --all-extras --no-install-project
          echo "Cache populated successfully"

      - name: Save uv caches
        uses: actions/cache/save@v4
        with:
          path: |
            ~/.cache/uv
            ~/.local/share/uv
            .venv
          key: uv-main-py${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
codeql matrix security .github/workflows/codeql.yml
Triggers
push, pull_request
Runs on
${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
Jobs
analyze
Matrix
include, include.build-mode, include.language→ actions, none, python
Actions
github/codeql-action/init, github/codeql-action/analyze
Commands
  • echo 'If you are using a "manual" build mode for one or more of the' \ 'languages you are analyzing, replace this with the commands to build' \ 'your code, for example:' echo ' make bootstrap' echo ' make release' exit 1
View raw YAML
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"

on:
  push:
    branches: [ "main" ]
    paths-ignore:
      - "lib/crewai/src/crewai/cli/templates/**"
  pull_request:
    branches: [ "main" ]
    paths-ignore:
      - "lib/crewai/src/crewai/cli/templates/**"

jobs:
  analyze:
    name: Analyze (${{ matrix.language }})
    # Runner size impacts CodeQL analysis time. To learn more, please see:
    #   - https://gh.io/recommended-hardware-resources-for-running-codeql
    #   - https://gh.io/supported-runners-and-hardware-resources
    #   - https://gh.io/using-larger-runners (GitHub.com only)
    # Consider using larger runners or machines with greater resources for possible analysis time improvements.
    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
    permissions:
      # required for all workflows
      security-events: write

      # required to fetch internal or private CodeQL packs
      packages: read

      # only required for workflows in private repositories
      actions: read
      contents: read

    strategy:
      fail-fast: false
      matrix:
        include:
        - language: actions
          build-mode: none
        - language: python
          build-mode: none
        # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
        # Use `c-cpp` to analyze code written in C, C++ or both
        # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
        # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
        # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
        # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
        # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
        # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    # Add any setup steps before running the `github/codeql-action/init` action.
    # This includes steps like installing compilers or runtimes (`actions/setup-node`
    # or others). This is typically only required for manual builds.
    # - name: Setup runtime (example)
    #   uses: actions/setup-example@v1

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v4
      with:
        languages: ${{ matrix.language }}
        build-mode: ${{ matrix.build-mode }}
        config-file: ./.github/codeql/codeql-config.yml
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.

        # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
        # queries: security-extended,security-and-quality

    # If the analyze step fails for one of the languages you are analyzing with
    # "We were unable to automatically build your code", modify the matrix above
    # to set the build mode to "manual" for that language. Then modify this step
    # to build your code.
    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
    - if: matrix.build-mode == 'manual'
      shell: bash
      run: |
        echo 'If you are using a "manual" build mode for one or more of the' \
          'languages you are analyzing, replace this with the commands to build' \
          'your code, for example:'
        echo '  make bootstrap'
        echo '  make release'
        exit 1

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v4
      with:
        category: "/language:${{matrix.language}}"
docs-broken-links .github/workflows/docs-broken-links.yml
Triggers
pull_request, push, workflow_dispatch
Runs on
ubuntu-latest
Jobs
check-links
Commands
  • npm i -g mintlify
  • # Auto-answer the prompt with yes command yes "" | mintlify broken-links || test $? -eq 141
View raw YAML
name: Check Documentation Broken Links

on:
  pull_request:
    paths:
      - "docs/**"
      - "docs.json"
  push:
    branches:
      - main
    paths:
      - "docs/**"
      - "docs.json"
  workflow_dispatch:

jobs:
  check-links:
    name: Check broken links
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: "22"

      - name: Install Mintlify CLI
        run: npm i -g mintlify

      - name: Run broken link checker
        run: |
          # Auto-answer the prompt with yes command
          yes "" | mintlify broken-links || test $? -eq 141
        working-directory: ./docs
generate-tool-specs perms .github/workflows/generate-tool-specs.yml
Triggers
pull_request, workflow_dispatch
Runs on
ubuntu-latest
Jobs
generate-specs
Actions
tibdex/github-app-token, astral-sh/setup-uv
Commands
  • uv sync --dev --all-extras
  • uv run python src/crewai_tools/generate_tool_specs.py
  • git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add lib/crewai-tools/tool.specs.json if git diff --quiet --staged; then echo "No changes detected in tool.specs.json" else echo "Changes detected in tool.specs.json, committing..." git commit -m "chore: update tool specifications" git push fi
View raw YAML
name: Generate Tool Specifications

on:
  pull_request:
    branches:
      - main
    paths:
      - 'lib/crewai-tools/src/crewai_tools/**'
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: write

jobs:
  generate-specs:
    runs-on: ubuntu-latest
    env:
      PYTHONUNBUFFERED: 1

    steps:
      - name: Generate GitHub App token
        id: app-token
        uses: tibdex/github-app-token@v2
        with:
          app_id: ${{ secrets.CREWAI_TOOL_SPECS_APP_ID }}
          private_key: ${{ secrets.CREWAI_TOOL_SPECS_PRIVATE_KEY }}

      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.head_ref }}
          token: ${{ steps.app-token.outputs.token }}

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          version: "0.8.4"
          python-version: "3.12"
          enable-cache: true

      - name: Install the project
        working-directory: lib/crewai-tools
        run: uv sync --dev --all-extras

      - name: Generate tool specifications
        working-directory: lib/crewai-tools
        run: uv run python src/crewai_tools/generate_tool_specs.py

      - name: Check for changes and commit
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

          git add lib/crewai-tools/tool.specs.json

          if git diff --quiet --staged; then
            echo "No changes detected in tool.specs.json"
          else
            echo "Changes detected in tool.specs.json, committing..."
            git commit -m "chore: update tool specifications"
            git push
          fi
linter perms .github/workflows/linter.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
lint
Actions
astral-sh/setup-uv
Commands
  • uv sync --all-groups --all-extras --no-install-project
  • uv run ruff check lib/
  • uv run ruff format --check lib/
View raw YAML
name: Lint

on: [pull_request]

permissions:
  contents: read

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Restore global uv cache
        id: cache-restore
        uses: actions/cache/restore@v4
        with:
          path: |
            ~/.cache/uv
            ~/.local/share/uv
            .venv
          key: uv-main-py3.11-${{ hashFiles('uv.lock') }}
          restore-keys: |
            uv-main-py3.11-

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          version: "0.8.4"
          python-version: "3.11"
          enable-cache: false

      - name: Install dependencies
        run: uv sync --all-groups --all-extras --no-install-project

      - name: Ruff check
        run: uv run ruff check lib/

      - name: Ruff format
        run: uv run ruff format --check lib/

      - name: Save uv caches
        if: steps.cache-restore.outputs.cache-hit != 'true'
        uses: actions/cache/save@v4
        with:
          path: |
            ~/.cache/uv
            ~/.local/share/uv
            .venv
          key: uv-main-py3.11-${{ hashFiles('uv.lock') }}
nightly .github/workflows/nightly.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
check, build, publish
Actions
astral-sh/setup-uv, astral-sh/setup-uv
Commands
  • RECENT=$(git log --since="24 hours ago" --oneline | head -1) if [ -n "$RECENT" ]; then echo "has_changes=true" >> "$GITHUB_OUTPUT" else echo "has_changes=false" >> "$GITHUB_OUTPUT" fi
  • DATE=$(date +%Y%m%d) for init_file in \ lib/crewai/src/crewai/__init__.py \ lib/crewai-tools/src/crewai_tools/__init__.py \ lib/crewai-files/src/crewai_files/__init__.py; do CURRENT=$(python -c " import re text = open('$init_file').read() print(re.search(r'__version__\s*=\s*\"(.*?)\"\s*$', text, re.MULTILINE).group(1)) ") NIGHTLY="${CURRENT}.dev${DATE}" sed -i "s/__version__ = .*/__version__ = \"${NIGHTLY}\"/" "$init_file" echo "$init_file: $CURRENT -> $NIGHTLY" done # Update cross-package dependency pins to nightly versions sed -i "s/\"crewai-tools==[^\"]*\"/\"crewai-tools==${NIGHTLY}\"/" lib/crewai/pyproject.toml sed -i "s/\"crewai==[^\"]*\"/\"crewai==${NIGHTLY}\"/" lib/crewai-tools/pyproject.toml echo "Updated cross-package dependency pins to ${NIGHTLY}"
  • uv build --all-packages rm dist/.gitignore
  • failed=0 for package in dist/*; do if [[ "$package" == *"crewai_devtools"* ]]; then echo "Skipping private package: $package" continue fi echo "Publishing $package" if ! uv publish "$package"; then echo "Failed to publish $package" failed=1 fi done if [ $failed -eq 1 ]; then echo "Some packages failed to publish" exit 1 fi
View raw YAML
name: Nightly Canary Release

on:
  schedule:
    - cron: '0 6 * * *' # daily at 6am UTC
  workflow_dispatch:

jobs:
  check:
    name: Check for new commits
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      has_changes: ${{ steps.check.outputs.has_changes }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Check for commits in last 24h
        id: check
        run: |
          RECENT=$(git log --since="24 hours ago" --oneline | head -1)
          if [ -n "$RECENT" ]; then
            echo "has_changes=true" >> "$GITHUB_OUTPUT"
          else
            echo "has_changes=false" >> "$GITHUB_OUTPUT"
          fi

  build:
    name: Build nightly packages
    needs: check
    if: needs.check.outputs.has_changes == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install uv
        uses: astral-sh/setup-uv@v4

      - name: Stamp nightly versions
        run: |
          DATE=$(date +%Y%m%d)
          for init_file in \
            lib/crewai/src/crewai/__init__.py \
            lib/crewai-tools/src/crewai_tools/__init__.py \
            lib/crewai-files/src/crewai_files/__init__.py; do
            CURRENT=$(python -c "
          import re
          text = open('$init_file').read()
          print(re.search(r'__version__\s*=\s*\"(.*?)\"\s*$', text, re.MULTILINE).group(1))
          ")
            NIGHTLY="${CURRENT}.dev${DATE}"
            sed -i "s/__version__ = .*/__version__ = \"${NIGHTLY}\"/" "$init_file"
            echo "$init_file: $CURRENT -> $NIGHTLY"
          done

          # Update cross-package dependency pins to nightly versions
          sed -i "s/\"crewai-tools==[^\"]*\"/\"crewai-tools==${NIGHTLY}\"/" lib/crewai/pyproject.toml
          sed -i "s/\"crewai==[^\"]*\"/\"crewai==${NIGHTLY}\"/" lib/crewai-tools/pyproject.toml
          echo "Updated cross-package dependency pins to ${NIGHTLY}"

      - name: Build packages
        run: |
          uv build --all-packages
          rm dist/.gitignore

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  publish:
    name: Publish nightly to PyPI
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/p/crewai
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          version: "0.8.4"
          python-version: "3.12"
          enable-cache: false

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist

      - name: Publish to PyPI
        env:
          UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        run: |
          failed=0
          for package in dist/*; do
            if [[ "$package" == *"crewai_devtools"* ]]; then
              echo "Skipping private package: $package"
              continue
            fi
            echo "Publishing $package"
            if ! uv publish "$package"; then
              echo "Failed to publish $package"
              failed=1
            fi
          done
          if [ $failed -eq 1 ]; then
            echo "Some packages failed to publish"
            exit 1
          fi
pr-size .github/workflows/pr-size.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
pr-size
Actions
codelytv/pr-size-labeler
View raw YAML
name: PR Size Check

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  pr-size:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: codelytv/pr-size-labeler@v1
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          xs_label: "size/XS"
          xs_max_size: 25
          s_label: "size/S"
          s_max_size: 100
          m_label: "size/M"
          m_max_size: 250
          l_label: "size/L"
          l_max_size: 500
          xl_label: "size/XL"
          fail_if_xl: false
          files_to_ignore: |
            uv.lock
            *.lock
            lib/crewai/src/crewai/cli/templates/**
            **/*.json
            **/test_durations/**
            **/cassettes/**
pr-title perms .github/workflows/pr-title.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
pr-title
Actions
amannn/action-semantic-pull-request
View raw YAML
name: PR Title Check

on:
  pull_request:
    types: [opened, edited, synchronize, reopened]

permissions:
  contents: read
  pull-requests: read

jobs:
  pr-title:
    runs-on: ubuntu-latest
    steps:
      - uses: amannn/action-semantic-pull-request@v5
        continue-on-error: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          types: |
            feat
            fix
            refactor
            perf
            test
            docs
            chore
            ci
            style
            revert
          requireScope: false
          subjectPattern: ^[a-z].+[^.]$
          subjectPatternError: >
            The PR title "{title}" does not follow conventional commit format.

            Expected: <type>(<scope>): <lowercase description without trailing period>

            Examples:
              feat(memory): add lancedb storage backend
              fix(agents): resolve deadlock in concurrent execution
              chore(deps): bump pydantic to 2.11.9
publish .github/workflows/publish.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest
Jobs
build, publish
Actions
astral-sh/setup-uv, astral-sh/setup-uv, slackapi/slack-github-action
Commands
  • if [ -n "${{ inputs.release_tag }}" ]; then echo "tag=${{ inputs.release_tag }}" >> $GITHUB_OUTPUT else echo "tag=" >> $GITHUB_OUTPUT fi
  • uv build --all-packages rm dist/.gitignore
  • failed=0 for package in dist/*; do if [[ "$package" == *"crewai_devtools"* ]]; then echo "Skipping private package: $package" continue fi echo "Publishing $package" if ! uv publish "$package"; then echo "Failed to publish $package" failed=1 fi done if [ $failed -eq 1 ]; then echo "Some packages failed to publish" exit 1 fi
  • payload=$(uv run python -c " import json, re, subprocess, sys with open('lib/crewai/src/crewai/__init__.py') as f: m = re.search(r\"__version__\s*=\s*[\\\"']([^\\\"']+)\", f.read()) version = m.group(1) if m else 'unknown' import os tag = os.environ.get('RELEASE_TAG') or version try: r = subprocess.run(['gh','release','view',tag,'--json','body','-q','.body'], capture_output=True, text=True, check=True) body = r.stdout.strip() except Exception: body = '' blocks = [ {'type':'section','text':{'type':'mrkdwn', 'text':f':rocket: \`crewai v{version}\` published to PyPI'}}, {'type':'section','text':{'type':'mrkdwn', 'text':f'<https://pypi.org/project/crewai/{version}/|View on PyPI> · <https://github.com/crewAIInc/crewAI/releases/tag/{tag}|Release notes>'}}, {'type':'divider'}, ] if body: heading, items = '', [] for line in body.split('\n'): line = line.strip() if not line: continue hm = re.match(r'^#{2,3}\s+(.*)', line) if hm: if heading and items: skip = heading in ('What\\'s Changed','') or 'Contributors' in heading if not skip: txt = f'*{heading}*\n' + '\n'.join(f'• {i}' for i in items) blocks.append({'type':'section','text':{'type':'mrkdwn','text':txt}}) heading, items = hm.group(1), [] elif line.startswith('- ') or line.startswith('* '): items.append(re.sub(r'\*\*([^*]*)\*\*', r'*\1*', line[2:])) if heading and items: skip = heading in ('What\\'s Changed','') or 'Contributors' in heading if not skip: txt = f'*{heading}*\n' + '\n'.join(f'• {i}' for i in items) blocks.append({'type':'section','text':{'type':'mrkdwn','text':txt}}) blocks.append({'type':'divider'}) blocks.append({'type':'section','text':{'type':'mrkdwn', 'text':f'\`\`\`uv add \"crewai[tools]=={version}\"\`\`\`'}}) print(json.dumps({'blocks':blocks})) ") echo "payload=$payload" >> $GITHUB_OUTPUT
View raw YAML
name: Publish to PyPI

on:
  workflow_dispatch:
    inputs:
      release_tag:
        description: 'Release tag to publish'
        required: false
        type: string

jobs:
  build:
    name: Build packages
    runs-on: ubuntu-latest
    permissions:
        contents: read
    steps:
      - name: Determine release tag
        id: release
        run: |
          if [ -n "${{ inputs.release_tag }}" ]; then
            echo "tag=${{ inputs.release_tag }}" >> $GITHUB_OUTPUT
          else
            echo "tag=" >> $GITHUB_OUTPUT
          fi

      - uses: actions/checkout@v4
        with:
          ref: ${{ steps.release.outputs.tag || github.ref }}

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install uv
        uses: astral-sh/setup-uv@v4

      - name: Build packages
        run: |
          uv build --all-packages
          rm dist/.gitignore

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  publish:
    name: Publish to PyPI
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/p/crewai
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ inputs.release_tag || github.ref }}

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          version: "0.8.4"
          python-version: "3.12"
          enable-cache: false

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist

      - name: Publish to PyPI
        env:
          UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        run: |
          failed=0
          for package in dist/*; do
            if [[ "$package" == *"crewai_devtools"* ]]; then
              echo "Skipping private package: $package"
              continue
            fi
            echo "Publishing $package"
            if ! uv publish "$package"; then
              echo "Failed to publish $package"
              failed=1
            fi
          done
          if [ $failed -eq 1 ]; then
            echo "Some packages failed to publish"
            exit 1
          fi

      - name: Build Slack payload
        if: success()
        id: slack
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          RELEASE_TAG: ${{ inputs.release_tag }}
        run: |
          payload=$(uv run python -c "
          import json, re, subprocess, sys

          with open('lib/crewai/src/crewai/__init__.py') as f:
              m = re.search(r\"__version__\s*=\s*[\\\"']([^\\\"']+)\", f.read())
          version = m.group(1) if m else 'unknown'

          import os
          tag = os.environ.get('RELEASE_TAG') or version

          try:
              r = subprocess.run(['gh','release','view',tag,'--json','body','-q','.body'],
                                 capture_output=True, text=True, check=True)
              body = r.stdout.strip()
          except Exception:
              body = ''

          blocks = [
              {'type':'section','text':{'type':'mrkdwn',
                  'text':f':rocket: \`crewai v{version}\` published to PyPI'}},
              {'type':'section','text':{'type':'mrkdwn',
                  'text':f'<https://pypi.org/project/crewai/{version}/|View on PyPI> · <https://github.com/crewAIInc/crewAI/releases/tag/{tag}|Release notes>'}},
              {'type':'divider'},
          ]

          if body:
              heading, items = '', []
              for line in body.split('\n'):
                  line = line.strip()
                  if not line: continue
                  hm = re.match(r'^#{2,3}\s+(.*)', line)
                  if hm:
                      if heading and items:
                          skip = heading in ('What\\'s Changed','') or 'Contributors' in heading
                          if not skip:
                              txt = f'*{heading}*\n' + '\n'.join(f'• {i}' for i in items)
                              blocks.append({'type':'section','text':{'type':'mrkdwn','text':txt}})
                      heading, items = hm.group(1), []
                  elif line.startswith('- ') or line.startswith('* '):
                      items.append(re.sub(r'\*\*([^*]*)\*\*', r'*\1*', line[2:]))
              if heading and items:
                  skip = heading in ('What\\'s Changed','') or 'Contributors' in heading
                  if not skip:
                      txt = f'*{heading}*\n' + '\n'.join(f'• {i}' for i in items)
                      blocks.append({'type':'section','text':{'type':'mrkdwn','text':txt}})

          blocks.append({'type':'divider'})
          blocks.append({'type':'section','text':{'type':'mrkdwn',
              'text':f'\`\`\`uv add \"crewai[tools]=={version}\"\`\`\`'}})

          print(json.dumps({'blocks':blocks}))
          ")
          echo "payload=$payload" >> $GITHUB_OUTPUT

      - name: Notify Slack
        if: success()
        uses: slackapi/slack-github-action@v2.1.0
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
          webhook-type: incoming-webhook
          payload: ${{ steps.slack.outputs.payload }}
stale perms .github/workflows/stale.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
stale
Actions
actions/stale
View raw YAML
name: Mark stale issues and pull requests

permissions:
  contents: write
  issues: write
  pull-requests: write

on:
  schedule:
    - cron: '10 12 * * *'
  workflow_dispatch:

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/stale@v9
      with:
        repo-token: ${{ secrets.GITHUB_TOKEN }}
        stale-issue-label: 'no-issue-activity'
        stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
        close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
        days-before-issue-stale: 30
        days-before-issue-close: 5
        stale-pr-label: 'no-pr-activity'
        stale-pr-message: 'This PR is stale because it has been open for 45 days with no activity.'
        days-before-pr-stale: 45
        days-before-pr-close: -1
        operations-per-run: 1200
tests matrix perms .github/workflows/tests.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
tests
Matrix
group, python-version→ 1, 2, 3, 3.10, 3.11, 3.12, 3.13, 4, 5, 6, 7, 8
Actions
astral-sh/setup-uv
Commands
  • uv sync --all-groups --all-extras
  • PYTHON_VERSION_SAFE=$(echo "${{ matrix.python-version }}" | tr '.' '_') DURATION_FILE="../../.test_durations_py${PYTHON_VERSION_SAFE}" # Temporarily always skip cached durations to fix test splitting # When durations don't match, pytest-split runs duplicate tests instead of splitting echo "Using even test splitting (duration cache disabled until fix merged)" DURATIONS_ARG="" # Original logic (disabled temporarily): # if [ ! -f "$DURATION_FILE" ]; then # echo "No cached durations found, tests will be split evenly" # DURATIONS_ARG="" # elif git diff origin/${{ github.base_ref }}...HEAD --name-only 2>/dev/null | grep -q "^tests/.*\.py$"; then # echo "Test files have changed, skipping cached durations to avoid mismatches" # DURATIONS_ARG="" # else # echo "No test changes detected, using cached test durations for optimal splitting" # DURATIONS_ARG="--durations-path=${DURATION_FILE}" # fi cd lib/crewai && uv run pytest \ -vv \ --splits 8 \ --group ${{ matrix.group }} \ $DURATIONS_ARG \ --durations=10 \ --maxfail=3
  • cd lib/crewai-tools && uv run pytest \ -vv \ --splits 8 \ --group ${{ matrix.group }} \ --durations=10 \ --maxfail=3
View raw YAML
name: Run Tests

on: [pull_request]

permissions:
  contents: read

jobs:
  tests:
    name: tests (${{ matrix.python-version }})
    runs-on: ubuntu-latest
    timeout-minutes: 15
    strategy:
      fail-fast: true
      matrix:
        python-version: ['3.10', '3.11', '3.12', '3.13']
        group: [1, 2, 3, 4, 5, 6, 7, 8]
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Fetch all history for proper diff

      - name: Restore global uv cache
        id: cache-restore
        uses: actions/cache/restore@v4
        with:
          path: |
            ~/.cache/uv
            ~/.local/share/uv
            .venv
          key: uv-main-py${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
          restore-keys: |
            uv-main-py${{ matrix.python-version }}-

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          version: "0.8.4"
          python-version: ${{ matrix.python-version }}
          enable-cache: false

      - name: Install the project
        run: uv sync --all-groups --all-extras

      - name: Restore test durations
        uses: actions/cache/restore@v4
        with:
          path: .test_durations_py*
          key: test-durations-py${{ matrix.python-version }}

      - name: Run tests (group ${{ matrix.group }} of 8)
        run: |
          PYTHON_VERSION_SAFE=$(echo "${{ matrix.python-version }}" | tr '.' '_')
          DURATION_FILE="../../.test_durations_py${PYTHON_VERSION_SAFE}"

          # Temporarily always skip cached durations to fix test splitting
          # When durations don't match, pytest-split runs duplicate tests instead of splitting
          echo "Using even test splitting (duration cache disabled until fix merged)"
          DURATIONS_ARG=""

          # Original logic (disabled temporarily):
          # if [ ! -f "$DURATION_FILE" ]; then
          #   echo "No cached durations found, tests will be split evenly"
          #   DURATIONS_ARG=""
          # elif git diff origin/${{ github.base_ref }}...HEAD --name-only 2>/dev/null | grep -q "^tests/.*\.py$"; then
          #   echo "Test files have changed, skipping cached durations to avoid mismatches"
          #   DURATIONS_ARG=""
          # else
          #   echo "No test changes detected, using cached test durations for optimal splitting"
          #   DURATIONS_ARG="--durations-path=${DURATION_FILE}"
          # fi

          cd lib/crewai && uv run pytest \
            -vv \
            --splits 8 \
            --group ${{ matrix.group }} \
            $DURATIONS_ARG \
            --durations=10 \
            --maxfail=3

      - name: Run tool tests (group ${{ matrix.group }} of 8)
        run: |
          cd lib/crewai-tools && uv run pytest \
            -vv \
            --splits 8 \
            --group ${{ matrix.group }} \
            --durations=10 \
            --maxfail=3


      - name: Save uv caches
        if: steps.cache-restore.outputs.cache-hit != 'true'
        uses: actions/cache/save@v4
        with:
          path: |
            ~/.cache/uv
            ~/.local/share/uv
            .venv
          key: uv-main-py${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
type-checker matrix perms .github/workflows/type-checker.yml
Triggers
pull_request
Runs on
ubuntu-latest, ubuntu-latest
Jobs
type-checker-matrix, type-checker
Matrix
python-version→ 3.10, 3.11, 3.12, 3.13
Actions
astral-sh/setup-uv
Commands
  • uv sync --all-groups --all-extras
  • uv run mypy lib/
  • if [ "${{ needs.type-checker-matrix.result }}" == "success" ] || [ "${{ needs.type-checker-matrix.result }}" == "skipped" ]; then echo "✅ All type checks passed" else echo "❌ Type checks failed" exit 1 fi
View raw YAML
name: Run Type Checks

on: [pull_request]

permissions:
  contents: read

jobs:
  type-checker-matrix:
    name: type-checker (${{ matrix.python-version }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.10", "3.11", "3.12", "3.13"]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Restore global uv cache
        id: cache-restore
        uses: actions/cache/restore@v4
        with:
          path: |
            ~/.cache/uv
            ~/.local/share/uv
            .venv
          key: uv-main-py${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
          restore-keys: |
            uv-main-py${{ matrix.python-version }}-

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          version: "0.8.4"
          python-version: ${{ matrix.python-version }}
          enable-cache: false

      - name: Install dependencies
        run: uv sync --all-groups --all-extras

      - name: Run type checks
        run: uv run mypy lib/

      - name: Save uv caches
        if: steps.cache-restore.outputs.cache-hit != 'true'
        uses: actions/cache/save@v4
        with:
          path: |
            ~/.cache/uv
            ~/.local/share/uv
            .venv
          key: uv-main-py${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}

  # Summary job to provide single status for branch protection
  type-checker:
    name: type-checker
    runs-on: ubuntu-latest
    needs: type-checker-matrix
    if: always()
    steps:
      - name: Check matrix results
        run: |
          if [ "${{ needs.type-checker-matrix.result }}" == "success" ] || [ "${{ needs.type-checker-matrix.result }}" == "skipped" ]; then
            echo "✅ All type checks passed"
          else
            echo "❌ Type checks failed"
            exit 1
          fi
update-test-durations matrix perms .github/workflows/update-test-durations.yml
Triggers
push, workflow_dispatch
Runs on
ubuntu-latest
Jobs
update-durations
Matrix
python-version→ 3.10, 3.11, 3.12, 3.13
Actions
astral-sh/setup-uv
Commands
  • uv sync --all-groups --all-extras
  • PYTHON_VERSION_SAFE=$(echo "${{ matrix.python-version }}" | tr '.' '_') uv run pytest --store-durations --durations-path=.test_durations_py${PYTHON_VERSION_SAFE} -n auto
View raw YAML
name: Update Test Durations

on:
  push:
    branches:
      - main
    paths:
      - 'tests/**/*.py'
  workflow_dispatch:

permissions:
  contents: read

jobs:
  update-durations:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.10', '3.11', '3.12', '3.13']
    env:
      OPENAI_API_KEY: fake-api-key
      PYTHONUNBUFFERED: 1
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Restore global uv cache
        id: cache-restore
        uses: actions/cache/restore@v4
        with:
          path: |
            ~/.cache/uv
            ~/.local/share/uv
            .venv
          key: uv-main-py${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
          restore-keys: |
            uv-main-py${{ matrix.python-version }}-

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          version: "0.8.4"
          python-version: ${{ matrix.python-version }}
          enable-cache: false

      - name: Install the project
        run: uv sync --all-groups --all-extras

      - name: Run all tests and store durations
        run: |
          PYTHON_VERSION_SAFE=$(echo "${{ matrix.python-version }}" | tr '.' '_')
          uv run pytest --store-durations --durations-path=.test_durations_py${PYTHON_VERSION_SAFE} -n auto
        continue-on-error: true

      - name: Save durations to cache
        if: always()
        uses: actions/cache/save@v4
        with:
          path: .test_durations_py*
          key: test-durations-py${{ matrix.python-version }}

      - name: Save uv caches
        if: steps.cache-restore.outputs.cache-hit != 'true'
        uses: actions/cache/save@v4
        with:
          path: |
            ~/.cache/uv
            ~/.local/share/uv
            .venv
          key: uv-main-py${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}