Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/opencode-review-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4033,6 +4033,8 @@ jobs:
# in the opencode.jsonc "openai" provider block.
OPENCODE_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
# Org secret NVIDIA_NIM_API_KEY preferred; fallback NVIDIA_API_KEY.
# opencode.jsonc expects env NVIDIA_API_KEY for nvidia-nim/* models.
NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY || secrets.NVIDIA_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
SHARE: "false"
Expand Down Expand Up @@ -4087,7 +4089,7 @@ jobs:
OPENCODE_DYNAMIC_RUN_TIMEOUT_CAP_SECONDS: "5400"
OPENCODE_DYNAMIC_TOTAL_BUDGET_CAP_SECONDS: "11700"
OPENCODE_DYNAMIC_MAX_CYCLES_CAP: "1"
OPENCODE_FREE_RUN_TIMEOUT_SECONDS: "600"
OPENCODE_FREE_RUN_TIMEOUT_SECONDS: "3600"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import re
from pathlib import Path

path = Path(".github/workflows/opencode-review-dispatch.yml")
text = path.read_text()

candidate_line = re.search(r"^\s*OPENCODE_MODEL_CANDIDATES:.*$", text, re.MULTILINE).group(0)
free_count = candidate_line.count("opencode-free/")

def value(name: str) -> int:
    return int(re.search(rf"^\s*{name}:\s*\"(\d+)\"$", text, re.MULTILINE).group(1))

free_window = free_count * value("OPENCODE_FREE_RUN_TIMEOUT_SECONDS")
budget = min(value("OPENCODE_TOTAL_RETRY_BUDGET_SECONDS"),
             value("OPENCODE_POOL_STEP_TIMEOUT_SECONDS"))

if free_window > budget:
    raise SystemExit(
        f"free candidates require {free_window}s, but the pool budget is {budget}s"
    )
PY

Repository: ContextualWisdomLab/.github

Length of output: 227


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- workflow references ---'
rg -n -C 8 \
  'OPENCODE_(MODEL_CANDIDATES|FREE_RUN_TIMEOUT_SECONDS|TOTAL_RETRY_BUDGET_SECONDS|POOL_STEP_TIMEOUT_SECONDS)|run_opencode_review_model_pool|free candidate|free 후보' \
  .github/workflows/opencode-review-dispatch.yml

printf '%s\n' '--- model-pool script references ---'
rg -n -C 12 \
  'OPENCODE_(MODEL_CANDIDATES|FREE_RUN_TIMEOUT_SECONDS|TOTAL_RETRY_BUDGET_SECONDS|POOL_STEP_TIMEOUT_SECONDS)|timeout|budget|candidate' \
  scripts/ci/run_opencode_review_model_pool.sh

printf '%s\n' '--- contract-test references ---'
rg -n -C 10 \
  'OPENCODE_(MODEL_CANDIDATES|FREE_RUN_TIMEOUT_SECONDS|TOTAL_RETRY_BUDGET_SECONDS|POOL_STEP_TIMEOUT_SECONDS)|run_opencode_review_model_pool|timeout|budget|candidate' \
  tests/test_opencode_agent_contract.py

printf '%s\n' '--- file outlines ---'
ast-grep outline scripts/ci/run_opencode_review_model_pool.sh --lang bash 2>/dev/null || true
ast-grep outline tests/test_opencode_agent_contract.py --lang python 2>/dev/null || true

Repository: ContextualWisdomLab/.github

Length of output: 50385


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- workflow references ---'
rg -n -C 8 \
  'OPENCODE_(MODEL_CANDIDATES|FREE_RUN_TIMEOUT_SECONDS|TOTAL_RETRY_BUDGET_SECONDS|POOL_STEP_TIMEOUT_SECONDS)|run_opencode_review_model_pool|free candidate|free 후보' \
  .github/workflows/opencode-review-dispatch.yml

printf '%s\n' '--- model-pool script references ---'
rg -n -C 12 \
  'OPENCODE_(MODEL_CANDIDATES|FREE_RUN_TIMEOUT_SECONDS|TOTAL_RETRY_BUDGET_SECONDS|POOL_STEP_TIMEOUT_SECONDS)|timeout|budget|candidate' \
  scripts/ci/run_opencode_review_model_pool.sh

printf '%s\n' '--- contract-test references ---'
rg -n -C 10 \
  'OPENCODE_(MODEL_CANDIDATES|FREE_RUN_TIMEOUT_SECONDS|OPENCODE_TOTAL_RETRY_BUDGET_SECONDS|OPENCODE_POOL_STEP_TIMEOUT_SECONDS)|run_opencode_review_model_pool|timeout|budget|candidate' \
  tests/test_opencode_agent_contract.py

printf '%s\n' '--- file outlines ---'
ast-grep outline scripts/ci/run_opencode_review_model_pool.sh --lang bash 2>/dev/null || true
ast-grep outline tests/test_opencode_agent_contract.py --lang python 2>/dev/null || true

Repository: ContextualWisdomLab/.github

Length of output: 50385


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import re
from pathlib import Path

workflow = Path(".github/workflows/opencode-review-dispatch.yml").read_text()
runner = Path("scripts/ci/run_opencode_review_model_pool.sh").read_text()

candidate_line = re.search(
    r'^\s*OPENCODE_MODEL_CANDIDATES:\s*"([^"]+)"$', workflow, re.MULTILINE
).group(1)
free_candidates = re.findall(r'\bopencode-free/\S+', candidate_line)
free_timeout = int(re.search(
    r'^\s*OPENCODE_FREE_RUN_TIMEOUT_SECONDS:\s*"(\d+)"$',
    workflow, re.MULTILINE
).group(1))
outer_timeout = int(re.search(
    r'^\s*OPENCODE_POOL_STEP_TIMEOUT_SECONDS:\s*"(\d+)"$',
    workflow, re.MULTILINE
).group(1))

# Extract the budgets selected by the dynamic-cadence branches in the runner.
branch_budgets = {
    "small": int(re.search(
        r'original_run_timeout=.*?'
        r'OPENCODE_SMALL_CHANGE_TOTAL_BUDGET_SECONDS (\d+)',
        runner, re.DOTALL
    ).group(1)),
    "medium": int(re.search(
        r'OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS (\d+)',
        runner
    ).group(1)),
    "large": int(re.search(
        r'OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS (\d+)',
        runner
    ).group(1)),
    "unknown": int(re.search(
        r'OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS (\d+)',
        runner
    ).group(1)),
}

print(f"free_candidates={len(free_candidates)}")
print(f"free_timeout={free_timeout}s")
print(f"configured_outer_timeout={outer_timeout}s")
print(f"free_max_window={len(free_candidates) * free_timeout}s")
print(f"dynamic_budgets={branch_budgets}")

# Model the runner's one-attempt, deadline-checked loop when every free
# candidate consumes its allowed timeout. This is arithmetic only; it does
# not execute repository code or contact providers.
for cadence, budget in branch_budgets.items():
    elapsed = 0
    attempted = 0
    for _ in free_candidates:
        if elapsed >= budget:
            break
        elapsed += min(free_timeout, budget - elapsed)
        attempted += 1
    print(
        f"{cadence}: attempted_free_candidates={attempted}, "
        f"elapsed={elapsed}s, fallback_reached={'yes' if attempted == len(free_candidates) else 'no'}"
    )
PY

printf '%s\n' '--- relevant contract-test range ---'
sed -n '260,380p' tests/test_opencode_agent_contract.py

Repository: ContextualWisdomLab/.github

Length of output: 6462


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import re
from pathlib import Path

workflow = Path(".github/workflows/opencode-review-dispatch.yml").read_text()
runner = Path("scripts/ci/run_opencode_review_model_pool.sh").read_text()

candidate_line = re.search(
    r'^\s*OPENCODE_MODEL_CANDIDATES:\s*"([^"]+)"$', workflow, re.MULTILINE
).group(1)
free_candidates = re.findall(r'\bopencode-free/\S+', candidate_line)
free_timeout = int(re.search(
    r'^\s*OPENCODE_FREE_RUN_TIMEOUT_SECONDS:\s*"(\d+)"$',
    workflow, re.MULTILINE
).group(1))
outer_timeout = int(re.search(
    r'^\s*OPENCODE_POOL_STEP_TIMEOUT_SECONDS:\s*"(\d+)"$',
    workflow, re.MULTILINE
).group(1))

branch_budgets = {
    "small": int(re.search(
        r'OPENCODE_SMALL_CHANGE_TOTAL_BUDGET_SECONDS (\d+)', runner
    ).group(1)),
    "medium": int(re.search(
        r'OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS (\d+)', runner
    ).group(1)),
    "large": int(re.search(
        r'OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS (\d+)', runner
    ).group(1)),
    "unknown": int(re.search(
        r'OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS (\d+)', runner
    ).group(1)),
}

print(f"free_candidates={len(free_candidates)}")
print(f"free_timeout={free_timeout}s")
print(f"configured_outer_timeout={outer_timeout}s")
print(f"free_max_window={len(free_candidates) * free_timeout}s")
print(f"dynamic_budgets={branch_budgets}")

for cadence, budget in branch_budgets.items():
    elapsed = 0
    attempted = 0
    for _ in free_candidates:
        if elapsed >= budget:
            break
        elapsed += min(free_timeout, budget - elapsed)
        attempted += 1
    print(
        f"{cadence}: attempted_free_candidates={attempted}, "
        f"elapsed={elapsed}s, fallback_reached="
        f"{'yes' if attempted == len(free_candidates) else 'no'}"
    )
PY

printf '%s\n' '--- relevant contract-test range ---'
sed -n '260,380p' tests/test_opencode_agent_contract.py

Repository: ContextualWisdomLab/.github

Length of output: 6462


Free 후보의 timeout과 동적 pool 예산을 함께 조정하세요.

공개 PR에서는 7개의 opencode-free/* 후보가 fallback보다 먼저 실행됩니다. 동적 cadence는 전체 예산을 2100초, 3900초 또는 7200초로 줄입니다. Free 후보가 timeout되면 1~2개만 실행한 뒤 pool이 종료되어 nvidia-nim/* 또는 유료 fallback에 도달하지 못합니다. Free timeout, 후보 수·순서 또는 전체 예산을 조정하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/opencode-review-dispatch.yml at line 4092, Adjust the
OPENCODE_FREE_RUN_TIMEOUT_SECONDS setting and the related dynamic pool budget or
candidate ordering so all seven opencode-free candidates can be attempted
without exhausting the pool before nvidia-nim or paid fallbacks run. Preserve
fallback execution by ensuring the combined free-candidate timeout fits within
each supported total budget of 2100, 3900, and 7200 seconds.

# This installation currently reports a 4k request-body limit for
# GitHub Models GPT-5 endpoints even though the public catalog is
# larger. Keep the exact runtime failure visible without spending a
Expand Down Expand Up @@ -4689,6 +4691,8 @@ jobs:
# Exposed so the "openai" provider in opencode.jsonc resolves during the
# failed-check diagnosis opencode run that shares this config.
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
# Org secret NVIDIA_NIM_API_KEY preferred; fallback NVIDIA_API_KEY.
# opencode.jsonc expects env NVIDIA_API_KEY for nvidia-nim/* models.
NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY || secrets.NVIDIA_API_KEY }}
OPENCODE_APP_TOKEN: ${{ steps.opencode_app_token.outputs.token }}
OPENCODE_EVIDENCE_FILE: ${{ runner.temp }}/opencode-review-evidence.md
Expand Down
8 changes: 5 additions & 3 deletions scripts/ci/run_opencode_review_model_pool.sh
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,10 @@ is_nvidia_nim_candidate() {
esac
}

# Org secret is NVIDIA_NIM_API_KEY; opencode.jsonc expects NVIDIA_API_KEY.
# Normalize once so skip checks and the provider env share one name.
# Org secret name is NVIDIA_NIM_API_KEY (GitHub Actions / org secrets UI).
# opencode.jsonc nvidia-nim provider block resolves {env:NVIDIA_API_KEY}.
# Workflow maps secrets.NVIDIA_NIM_API_KEY || secrets.NVIDIA_API_KEY → env NVIDIA_API_KEY.
# Normalize here too so local/CLI runs with only NVIDIA_NIM_API_KEY set do not skip nim/*.
if [ -z "${NVIDIA_API_KEY:-}" ] && [ -n "${NVIDIA_NIM_API_KEY:-}" ]; then
export NVIDIA_API_KEY="$NVIDIA_NIM_API_KEY"
fi
Expand Down Expand Up @@ -399,7 +401,7 @@ cap_model_run_timeout() {

case "$model_candidate" in
opencode-free/*)
cap_seconds="$(env_integer_or_default OPENCODE_FREE_RUN_TIMEOUT_SECONDS 600)"
cap_seconds="$(env_integer_or_default OPENCODE_FREE_RUN_TIMEOUT_SECONDS 3600)"
;;
github-models/openai/gpt-5 | github-models/openai/gpt-5-chat)
cap_seconds="$(env_integer_or_default OPENCODE_GITHUB_GPT5_RUN_TIMEOUT_SECONDS 45)"
Expand Down
3 changes: 2 additions & 1 deletion scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -708,9 +708,10 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$workflow_file" 'timeout-minutes: 34' "opencode fast approval publication is bounded around the dynamic image and package/GPU check wait"
assert_file_contains "$workflow_file" 'continue-on-error: true' "opencode approval gate still runs after model-pool failure to publish a reason"
assert_file_contains "$workflow_file" 'OPENCODE_RUN_TIMEOUT_SECONDS: "5400"' "opencode primary review preserves legitimate full-hour provider sessions"
assert_file_contains "$workflow_file" 'OPENCODE_FREE_RUN_TIMEOUT_SECONDS: "3600"' "opencode free-tier failover timeout is hour-class (~3600s)"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_RUN_TIMEOUT_SECONDS:-3600' "opencode pool defaults primary run timeout to hour-class (~3600s) for large repos"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_DYNAMIC_RUN_TIMEOUT_CAP_SECONDS 3600' "opencode pool dynamic timeout cap defaults to hour-class (~3600s)"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_FREE_RUN_TIMEOUT_SECONDS 600' "opencode free-tier failover timeout stays short"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_FREE_RUN_TIMEOUT_SECONDS 3600' "opencode free-tier failover timeout is hour-class (~3600s)"

assert_file_contains "$workflow_file" 'OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "11700"' "opencode model pool exits before the step timeout so the approval gate can publish a reason"
assert_file_contains "$workflow_file" 'OPENCODE_POOL_MAX_CYCLES: "1"' "opencode model pool exhausts each candidate only once before bounded fallback"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_opencode_agent_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -1374,7 +1374,7 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent():
assert 'OPENCODE_DYNAMIC_RUN_TIMEOUT_CAP_SECONDS: "5400"' in workflow
assert 'OPENCODE_DYNAMIC_TOTAL_BUDGET_CAP_SECONDS: "11700"' in workflow
assert 'OPENCODE_DYNAMIC_MAX_CYCLES_CAP: "1"' in workflow
assert 'OPENCODE_FREE_RUN_TIMEOUT_SECONDS: "600"' in workflow
assert 'OPENCODE_FREE_RUN_TIMEOUT_SECONDS: "3600"' in workflow
assert 'OPENCODE_GITHUB_GPT5_RUN_TIMEOUT_SECONDS: "45"' in workflow
assert 'OPENCODE_DYNAMIC_MAX_CYCLES: "1"' in workflow
assert 'OPENCODE_BACKOFF_MAX_SECONDS: "30"' in workflow
Expand Down
Loading