Describe the bug
anthropics/claude-code-action does not pass the BASH_MAX_TIMEOUT_MS and BASH_DEFAULT_TIMEOUT_MS environment variable from the job environment into the Claude Code execution environment. As a result, Bash tool timeouts ignore the value set at the workflow/job level (defaults to 2 minutes).
To Reproduce
- Define BASH_MAX_TIMEOUT_MS at the job level (or step env:) to a large value.
- Run a Claude Code step that executes a long-running Bash command.
- Observe that the command still times out at the default limit rather than using the provided value.
Expected behavior
Claude’s Bash tool should see BASH_MAX_TIMEOUT_MS=900000 in its environment and allow commands to run up to 15 minutes before timing out. Same for BASH_DEFAULT_TIMEOUT_MS
Screenshots
N/A
Workflow yml file
# This action deletes feature toggles from multiple repositories using AI (Claude Code).
# It creates a pull request in each repository to remove the specified feature toggle branch.
# Learn more: https://docs.google.com/document/d/1oX2n-fxIUpPzIyVl_GbLdnD_FHiw06bTjH1IGHcE8N8/edit?tab=t.0
name: Feature Toggle Slayer - Claude Code
on:
workflow_dispatch:
inputs:
repos_json:
description: |
A JSON array with repository configurations for feature toggle removal.
Example:
[
{ "repo": "Glovo/repo1", "toggle_name": "SHOULD_WORK", "toggle_value_to_delete": false },
{ "repo": "Glovo/repo2", "toggle_name": "SHOULD_SHOW", "toggle_value_to_delete": true }
]
required: true
type: string
jobs:
feature-toggle-slayer:
runs-on: glovo-shared-small-arm
timeout-minutes: 150 # 2 hours and a half
permissions:
contents: write
pull-requests: write
id-token: write
actions: read
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(inputs.repos_json) }}
steps:
- name: Generate GitHub token
id: generate_github_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.GLOVO_GH_ACTIONS_APP_ID }}
private-key: ${{ secrets.GLOVO_GH_ACTIONS_PRIVATE_KEY }}
owner: Glovo
- name: Checkout ${{ matrix.repo }}
uses: actions/checkout@v4
with:
repository: ${{ matrix.repo }}
token: ${{ steps.generate_github_token.outputs.token }}
- uses: Glovo/secrets-to-env-action@v1.6
name: Load Secrets on envs
id: load-secrets
with:
secrets: ${{ toJSON(secrets) }}
- name: Login to Artifactory docker-hub remote repository
uses: docker/login-action@v3
with:
registry: glovo-docker.artifactory.glovoint.com/
username: ${{ secrets.ARTIFACTORY_DOCKER_USERNAME }}
password: ${{ secrets.ARTIFACTORY_DOCKER_PASSWORD }}
- name: Set up JDK for Gradle
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'gradle'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
gradle-home-cache-cleanup: true
cache-read-only: false
- name: Setup environment for Gradle
run: |
echo 'GRADLE_OPTS=-Dorg.gradle.jvmargs="-Xmx2g -XX:+UseParallelGC" -Dorg.gradle.daemon=false' >> $GITHUB_ENV
echo 'CI=true' >> $GITHUB_ENV
echo 'TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX=glovo-docker.artifactory.glovoint.com/' >> $GITHUB_ENV
echo 'ORG_GRADLE_PROJECT_artifactory_user=${{ secrets.ARTIFACTORY_USERNAME }}' >> $GITHUB_ENV
echo 'ORG_GRADLE_PROJECT_artifactory_key=${{ secrets.ARTIFACTORY_PASSWORD }}' >> $GITHUB_ENV
- name: Ensure gradlew is executable and show version
run: |
if [ -f ./gradlew ]; then chmod +x ./gradlew; fi
./gradlew --version || true
- name: Run Claude Code AI to delete feature toggle
env:
ANTHROPIC_BASE_URL: ${{ vars.ANTHROPIC_BASE_URL }}
ANTHROPIC_MODEL: ${{ vars.ANTHROPIC_MODEL }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_AUTH_TOKEN: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ steps.generate_github_token.outputs.token }}
BASH_DEFAULT_TIMEOUT_MS: "7200000" # 120 minutes
BASH_MAX_TIMEOUT_MS: "7200000" # 120 minutes
id: claude
uses: anthropics/claude-code-action@v1
with:
github_token: ${{ steps.generate_github_token.outputs.token }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
use_commit_signing: true
claude_args: --model ${{ vars.ANTHROPIC_MODEL }} --allowedTools Bash,Edit,MultiEdit,Read,Task,TodoWrite,Write --permission-mode acceptEdits --verbose
prompt: |
Your task is to remove a feature toggle from the codebase.
Remove all code that executes when the toggle equals ${{ matrix.toggle_value_to_delete }}, keeping only the opposite branch.
Context:
Toggle uses FeatureToggleClient#getBooleanValue("${{ matrix.toggle_name }}")
Instructions:
Find the toggle: Search for all occurrences of ${{ matrix.toggle_name }} in the codebase. Look for conditional statements using this toggle.
Remove the toggle and keep correct path.
For each conditional checking the toggle, remove the conditional and the ${{ matrix.toggle_value_to_delete }} branch
Keep only the code that executes when the toggle has the opposite value
Ensure remaining code is properly indented and integrated
Do not add any new comments to the code.
I cannot do anything locally, so you must provide exact code changes that do not require any manual intervention from me.
Do not use sub-agents.
Clean up:
Remove unused imports, variables, or helper functions only used by the toggle logic.
Verify:
Confirm all instances of ${{ matrix.toggle_name }} are removed
Ensure no other toggles were accidentally modified
Check that code logic remains valid
IMPORTANT: Invariant (must not be violated): You must always validate that the code compiles, tests and
integration test pass (usually build test intTest).
Try to run these only on the modified modules / classes so that we have faster feedback loops.
If the commands takes too long, you need to wait for the completion. It's very important that this is considered.
If any of these steps fail, fix the issues before completing and then re-verify until all pass.
Keep output concise: always use `--console=plain --quiet` so only errors are printed.
On failure, re-run ONLY the failing tests / integration tests.
After code compiles, test and integration test passes, ensure the modified code is propely formatted by running
spotlessApply.
- name: Extract Claude execution metrics
id: claude_metrics
run: |
set -euo pipefail
# Find the output file
if [[ -f "/runner/_work/_temp/claude-execution-output.json" ]]; then
OUTPUT_FILE="/runner/_work/_temp/claude-execution-output.json"
else
printf '%s' '${{ steps.claude.outputs.result }}' > /tmp/claude_output.json
OUTPUT_FILE="/tmp/claude_output.json"
fi
# Extract all metrics with safe defaults
echo "is_error=$(jq -r '.is_error // false' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "status=$(jq -r '.subtype // .type // "unknown"' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "total_cost=$(jq -r '.total_cost_usd // 0' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "duration_ms=$(jq -r '.duration_ms // 0' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "api_duration_ms=$(jq -r '.duration_api_ms // 0' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "num_turns=$(jq -r '.num_turns // 0' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "input_tokens=$(jq -r '.usage.input_tokens // 0' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "output_tokens=$(jq -r '.usage.output_tokens // 0' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "cache_creation_tokens=$(jq -r '.usage.cache_creation_input_tokens // 0' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "cache_read_tokens=$(jq -r '.usage.cache_read_input_tokens // 0' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "service_tier=$(jq -r '.usage.service_tier // "standard"' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "web_searches=$(jq -r '.usage.server_tool_use.web_search_requests // 0' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
echo "session_id=$(jq -r '.session_id // "N/A"' "$OUTPUT_FILE")" >> "$GITHUB_OUTPUT"
# Calculate total tokens
TOTAL_TOKENS=$(jq -r '
(.usage.input_tokens // 0) +
(.usage.output_tokens // 0) +
(.usage.cache_creation_input_tokens // 0) +
(.usage.cache_read_input_tokens // 0)
' "$OUTPUT_FILE")
echo "total_tokens=$TOTAL_TOKENS" >> "$GITHUB_OUTPUT"
# Calculate cost per 1k tokens
COST_PER_1K=$(jq -r '
if (.total_cost_usd // 0) > 0 and
((.usage.input_tokens // 0) + (.usage.output_tokens // 0) +
(.usage.cache_creation_input_tokens // 0) + (.usage.cache_read_input_tokens // 0)) > 0
then
((.total_cost_usd // 0) * 1000) /
((.usage.input_tokens // 0) + (.usage.output_tokens // 0) +
(.usage.cache_creation_input_tokens // 0) + (.usage.cache_read_input_tokens // 0))
else 0 end
' "$OUTPUT_FILE")
echo "cost_per_1k_tokens=$COST_PER_1K" >> "$GITHUB_OUTPUT"
# Extract the result (multiline, so use EOF delimiter)
echo "result<<EOF" >> "$GITHUB_OUTPUT"
jq -r '.result // "No summary available"' "$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
- name: Generate GitHub token as it might be expired
id: generate_github_token_2
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.GLOVO_GH_ACTIONS_APP_ID }}
private-key: ${{ secrets.GLOVO_GH_ACTIONS_PRIVATE_KEY }}
owner: Glovo
- name: Create PR
id: cpr
uses: peter-evans/create-pull-request@v7
with:
token: ${{ steps.generate_github_token_2.outputs.token }}
commit-message: 'Remove feature toggle `${{ matrix.toggle_name }}`'
committer: Claude Code[bot] <claudio@anthropics.com>
author: Claude Code[bot] <claudio@anthropics.com>
title: 'Remove feature toggle `${{ matrix.toggle_name }}`'
labels: feature-toggle-slayer
branch: feature-toggle-slayer/claude/${{ matrix.toggle_name }}-removal
add-paths: '**/src/**'
body: |
## 📝 Summary
This pull request removes the feature toggle `${{ matrix.toggle_name }}` from the codebase, deleting all code paths where the toggle equals `${{ matrix.toggle_value_to_delete }}`.
> [!WARNING]
> This pull request was created by Claude Code AI agent using ${{ vars.ANTHROPIC_MODEL }} 🤖 (_[see workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})_), please review carefully and merge or reject as you see fit.
> *It is your responsability as a developer to monitor the deployment in case you merge the changes*.
## 🤖 Claude Code Summary
${{ steps.claude_metrics.outputs.result }}
### 💰 Cost Analysis
| Metric | Value |
|--------|--------|
| **Total Cost** | **${{ steps.claude_metrics.outputs.total_cost }} USD** |
| **Cost per 1K tokens** | ${{ steps.claude_metrics.outputs.cost_per_1k_tokens }} USD |
| **Service Tier** | ${{ steps.claude_metrics.outputs.service_tier }} |
| **Status** | ${{ steps.claude_metrics.outputs.status }} ✅ |
### 🔢 Token Usage
| Token Type | Count | Purpose |
|------------|-------|---------|
| **Input Tokens** | ${{ steps.claude_metrics.outputs.input_tokens }} | New content processed |
| **Output Tokens** | ${{ steps.claude_metrics.outputs.output_tokens }} | Generated by Claude |
| **Cache Creation** | ${{ steps.claude_metrics.outputs.cache_creation_tokens }} | Built context cache |
| **Cache Read** | ${{ steps.claude_metrics.outputs.cache_read_tokens }} | Read from cache (cheaper) |
| **Total Tokens** | ${{ steps.claude_metrics.outputs.total_tokens }} | Combined usage |
### ⚡ Performance
| Metric | Value |
|--------|--------|
| **Total Duration** | ${{ steps.claude_metrics.outputs.duration_ms }}ms |
| **API Calls Duration** | ${{ steps.claude_metrics.outputs.api_duration_ms }}ms |
| **Conversation Turns** | ${{ steps.claude_metrics.outputs.num_turns }} |
| **Web Searches** | ${{ steps.claude_metrics.outputs.web_searches }} |
<details>
<summary>🔍 Technical Details</summary>
- **Session ID**: `${{ steps.claude_metrics.outputs.session_id }}`
- **Workflow Run**: [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
- **Triggered by**: @${{ github.actor }}
- **Repository**: ${{ github.repository }}
- **Branch**: `${{ github.ref_name }}`
</details>
- name: PR URL
if: steps.cpr.outputs.pull-request-url != ''
run: echo "PR => ${{ steps.cpr.outputs.pull-request-url }}"
API Provider
LiteLLM
Describe the bug
anthropics/claude-code-actiondoes not pass theBASH_MAX_TIMEOUT_MSandBASH_DEFAULT_TIMEOUT_MSenvironment variable from the job environment into the Claude Code execution environment. As a result, Bash tool timeouts ignore the value set at the workflow/job level (defaults to 2 minutes).To Reproduce
Expected behavior
Claude’s Bash tool should see
BASH_MAX_TIMEOUT_MS=900000in its environment and allow commands to run up to 15 minutes before timing out. Same forBASH_DEFAULT_TIMEOUT_MSScreenshots
N/A
Workflow yml file
API Provider
LiteLLM