Every PR that touches a system prompt or model config runs the full CAI benchmark and posts the strain score as a comment. Set a threshold. Block the merge if it breaks. Six lines of YAML.
OPENAI_API_KEY. If you're running against an Anthropic model, use ANTHROPIC_API_KEY instead. The action supports both..github/workflows/contradish.yml in your repo. Edit the paths block to match wherever your system prompts and model configs live.name: contradish · CAI strain check on: pull_request: paths: - 'prompts/**' # system prompts - '**/*prompt*.txt' # any file with "prompt" in the name - '**/*system*.txt' # system instruction files - 'config/**/*.json' # model config files # add any other paths where your prompts or model settings live jobs: strain-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install contradish run: pip install contradish - name: Run CAI benchmark id: cai env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | mkdir -p results contradish benchmark --model gpt-4o --output-json results/cai.json --judge-votes ${{ vars.CONTRADISH_JUDGE_VOTES || 3 }} STRAIN=$(python -c "import json; d=json.load(open('results/cai.json')); print(d['judgment_strain'])") CRITICAL=$(python -c "import json; d=json.load(open('results/cai.json')); print(d.get('critical_count', 0))") CONFIDENCE=$(python -c "import json; d=json.load(open('results/cai.json')); c=d.get('judge_confidence'); print(c if c is not None else '')") VOTES_CAST=$(python -c "import json; d=json.load(open('results/cai.json')); v=d.get('avg_judge_votes_cast'); print(v if v is not None else '')") ORDER_SENSITIVE=$(python -c "import json; d=json.load(open('results/cai.json')); print(d.get('judge_order_sensitive_cases', 0))") echo "strain=$STRAIN" >> $GITHUB_OUTPUT echo "critical=$CRITICAL" >> $GITHUB_OUTPUT echo "confidence=$CONFIDENCE" >> $GITHUB_OUTPUT echo "votes_cast=$VOTES_CAST" >> $GITHUB_OUTPUT echo "order_sensitive=$ORDER_SENSITIVE" >> $GITHUB_OUTPUT - name: Post PR comment uses: actions/github-script@v7 with: script: | const strain = parseFloat('${{ steps.cai.outputs.strain }}'); const critical = parseInt('${{ steps.cai.outputs.critical }}'); const confidenceRaw = '${{ steps.cai.outputs.confidence }}'; const confidence = confidenceRaw ? parseFloat(confidenceRaw) : null; const votesCastRaw = '${{ steps.cai.outputs.votes_cast }}'; const votesCast = votesCastRaw ? parseFloat(votesCastRaw) : null; const judgeVoteCap = '${{ vars.CONTRADISH_JUDGE_VOTES || 3 }}'; const orderSensitive = parseInt('${{ steps.cai.outputs.order_sensitive }}' || '0'); const threshold = parseFloat('${{ vars.CONTRADISH_THRESHOLD || "0.35" }}'); const pass = strain <= threshold; const emoji = strain <= 0.20 ? '✅' : strain <= threshold ? '⚠️' : '❌'; const status = strain <= 0.20 ? 'passing' : strain <= threshold ? 'review' : 'failing'; const lowConfidence = confidence !== null && confidence < 0.7; await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: [ `## ${emoji} contradish · CAI Strain Check`, '', '| metric | value |', '|--------|-------|', `| judgment_strain | \`${strain.toFixed(3)}\` |`, `| threshold | \`${threshold}\` |`, `| critical failures | \`${critical}\` |`, confidence !== null ? `| judge confidence | \`${confidence.toFixed(2)}\` (avg ${votesCast !== null ? votesCast.toFixed(1) : '?'}/${judgeVoteCap} votes cast) |` : null, orderSensitive > 0 ? `| order-sensitive cases | \`${orderSensitive}\` |` : null, `| status | **${status}** |`, '', lowConfidence ? `⚠️ **Low judge confidence (${confidence.toFixed(2)}).** The judge disagreed with itself across votes on several cases -- treat this result as provisional and consider re-running before acting on it.` : null, lowConfidence ? '' : null, orderSensitive > 0 ? `📐 **${orderSensitive} case(s) were order-sensitive** -- the judge's verdict flipped when the same evidence was shown in a different order, not because the content changed. Those specific results are the least trustworthy in this run.` : null, orderSensitive > 0 ? '' : null, `Run \`contradish findings results/cai.json\` locally for the full diagnosis.`, '', '_[contradish](https://contradish.com) · memory-aware contradiction detection_' ].filter(Boolean).join('\n') }); - name: Enforce strain threshold if: steps.cai.outputs.strain > (vars.CONTRADISH_THRESHOLD || '0.35') run: | echo "❌ Judgment Strain ${{ steps.cai.outputs.strain }} exceeds threshold" echo "Run: contradish improve --policy <domain> --model gpt-4o --target-strain 0.20" exit 1
| metric | value |
|---|---|
| judgment_strain | 0.312 |
| threshold | 0.35 |
| critical failures | 3 |
| judge confidence | 0.93 (avg 2.1/3 votes cast) |
| status | review |
contradish findings results/cai.json locally for the full diagnosis and repair suggestions.Set a repository variable to change the threshold without editing the YAML. All options can be overridden per-repo or per-environment.
| variable | what it controls | default |
|---|---|---|
| CONTRADISH_THRESHOLD | Judgment Strain above this value fails the check and blocks merge. Set in Settings → Variables → Actions. | 0.35 |
| paths: | Which file changes trigger the action. Edit directly in the YAML to match your repo's prompt and config locations. | prompts/** |
| --model | Which model to benchmark. Supports gpt-4o, claude-sonnet-4-6, and all models supported by contradish benchmark. |
gpt-4o |
| OPENAI_API_KEY ANTHROPIC_API_KEY |
Model API key. Add as a repository secret, not a variable. Secrets are redacted in logs. | required |
To run contradish against a custom system prompt file rather than the built-in benchmark, add --prompt-file prompts/system.txt to the benchmark command. The action will measure strain on that specific prompt across the full adversarial suite.
Each benchmark run produces a results/cai.json artifact. When you run contradish improve on a failure, it writes a fine-tuning pair back to that file. Over time, those artifacts are your corpus: the verifiable record of how your model behaved, what broke it, and what fixed it. No other tool holds that record.
Running contradish Cloud? CI results sync to your shared dashboard automatically, with per-PR strain history, trend lines, and compliance export. Join the waitlist →