Personal
$10/month
- 50M tokens a week
- 20 requests per minute
- For one person
Add one step after your tests. When they fail, a headless agent reads the log, fixes the code and reruns the suite. Your script keeps the patch only if the tests pass untouched. The agent runs on Pareto, Unbiased’s model, which selects frontier and open models for each request and blends their outputs into one answer.
Tested live with Codex CLI 0.162.1 and Factory Droid 0.245.050M tokens a week
Each of these talks to Pareto with a base URL, an API key and the model string pareto. Pick the one you already use.
codex exec runs one task with no prompts, edits inside a workspace sandbox and can log every step as JSON.Pareto in OpenAI Codex CLI: setup guide →droid exec runs one task at a set autonomy level. --auto medium lets it edit files and run tests, but not push.Pareto in Factory Droid: setup guide →Both versions do the same thing. Your test step saves its output to ci.log. If it fails, a short script hands the log to the agent, then reruns the tests itself and checks that nothing under tests/ changed. Only then does it write autofix.patch. The build stays red, so a person still reads the patch before anything merges.
Choose a plan, then create a key in the Unbiased platform. New accounts are reviewed and approved by a person before the key works. In GitHub, save it as the repository secret UNBIASED_API_KEY.
Run your suite with 2>&1 | tee ci.log and set shell: bash on that step. GitHub only adds pipefail when the shell is named, and without it the step passes even when the tests fail.
Codex reads a custom provider from ~/.codex/config.toml; Droid reads a custom model from ~/.factory/settings.json. Both use base URL https://api.unbiased.ai/v1 and the model name pareto, typed by hand: Unbiased does not list models at /v1/models.
Give the step if: failure() && steps.tests.outcome == 'failure' and pass the key to that step alone. The prompt names the log, says to fix the code and not the tests, and asks the agent to rerun the suite.
Do not take the agent’s word for it. The script reruns python3 -m unittest, fails if git diff --exit-code -- tests finds a change, and saves git diff as an artifact for a person to apply.
pareto. Both tools read the CI log, changed one line in shop/pages.py and left the tests alone, and all 6 tests passed on a clean rerun, 3 runs out of 3 each. Run record.codex exec -s workspace-write lets Codex edit files and run commands inside the checkout without asking. --json writes every step to codex.jsonl, which you can keep as a log of what it ran. --full-auto is gone in current releases; use -s workspace-write. < /dev/null stops Codex from waiting for more input on stdin.
npm install -g @openai/codex@0.162.1sysctl line is the same change openai/codex-action makes on GitHub-hosted Ubuntu runners so the sandbox can startignore_default_excludes = false keeps variables named like *KEY* out of the commands Codex runs. Without it, our test command could read UNBIASED_API_KEYmodel = "pareto" model_provider = "unbiased" [model_providers.unbiased] name = "Unbiased" base_url = "https://api.unbiased.ai/v1" env_key = "UNBIASED_API_KEY" wire_api = "responses" [shell_environment_policy] ignore_default_excludes = false
#!/bin/sh # Runs after the test step fails. ci.log holds the failing output. set -e PROMPT="CI failed: python3 -m unittest exited 1 and its output is in ci.log." PROMPT="$PROMPT Find the cause and fix the code, not the tests." PROMPT="$PROMPT Then run python3 -m unittest and confirm every test passes." codex exec -s workspace-write --json "$PROMPT" < /dev/null > codex.jsonl python3 -m unittest # the fix has to pass on a clean rerun git diff --exit-code -- tests # and must leave the tests alone git diff > autofix.patch
name: test
on: push
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Run tests
id: tests
shell: bash
run: python3 -m unittest 2>&1 | tee ci.log
- name: Ask Codex for a fix
if: failure() && steps.tests.outcome == 'failure'
env:
UNBIASED_API_KEY: ${{ secrets.UNBIASED_API_KEY }}
run: |
npm install -g @openai/codex@0.162.1
# Ubuntu 24.04 blocks the user namespaces the Codex sandbox needs
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
mkdir -p ~/.codex
cat > ~/.codex/config.toml <<'TOML'
model = "pareto"
model_provider = "unbiased"
[model_providers.unbiased]
name = "Unbiased"
base_url = "https://api.unbiased.ai/v1"
env_key = "UNBIASED_API_KEY"
wire_api = "responses"
[shell_environment_policy]
ignore_default_excludes = false
TOML
sh .github/autofix-codex.sh
- uses: actions/upload-artifact@v4
if: failure() && steps.tests.outcome == 'failure'
with:
name: autofix
path: autofix.patchWe ran the config and script above on macOS, on a fresh copy of a failing repo with an isolated home folder. We have not run this workflow on GitHub or tested Codex’s sandbox on Linux, so treat the workflow as a starting point: it installs Codex, opens up the user namespaces its sandbox needs, writes the same config and calls the same script.
droid exec --auto medium may edit files, install trusted packages and run builds and tests, and stops before git push. -o json writes the final result, turn count and token usage to droid.json. Pick the model with custom:Pareto-0: the display name, then its position in customModels starting at 0.
curl -fsSL https://app.factory.ai/cli | shFACTORY_API_KEY. Factory’s docs say to set FACTORY_API_KEY, so the workflow passes it from a repository secret{
"customModels": [
{
"model": "pareto",
"displayName": "Pareto",
"baseUrl": "https://api.unbiased.ai/v1",
"apiKey": "${UNBIASED_API_KEY}",
"provider": "generic-chat-completion-api",
"maxOutputTokens": 32000
}
]
}#!/bin/sh # Runs after the test step fails. ci.log holds the failing output. set -e PROMPT="CI failed: python3 -m unittest exited 1 and its output is in ci.log." PROMPT="$PROMPT Find the cause and fix the code, not the tests." PROMPT="$PROMPT Then run python3 -m unittest and confirm every test passes." droid exec --auto medium --model "custom:Pareto-0" -o json "$PROMPT" > droid.json python3 -m unittest # the fix has to pass on a clean rerun git diff --exit-code -- tests # and must leave the tests alone git diff > autofix.patch
name: test
on: push
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Run tests
id: tests
shell: bash
run: python3 -m unittest 2>&1 | tee ci.log
- name: Ask Droid for a fix
if: failure() && steps.tests.outcome == 'failure'
env:
UNBIASED_API_KEY: ${{ secrets.UNBIASED_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
run: |
curl -fsSL https://app.factory.ai/cli | sh
export PATH="$HOME/.local/bin:$PATH"
mkdir -p ~/.factory
cat > ~/.factory/settings.json <<'JSON'
{
"customModels": [
{
"model": "pareto",
"displayName": "Pareto",
"baseUrl": "https://api.unbiased.ai/v1",
"apiKey": "${UNBIASED_API_KEY}",
"provider": "generic-chat-completion-api",
"maxOutputTokens": 32000
}
]
}
JSON
sh .github/autofix-droid.sh
- uses: actions/upload-artifact@v4
if: failure() && steps.tests.outcome == 'failure'
with:
name: autofix
path: autofix.patchWe ran the settings and script above on a fresh copy of a failing repo with an isolated home folder. We have not run the workflow file on GitHub: it installs Droid, writes the same settings and calls the same script. Commands Droid runs can read UNBIASED_API_KEY, so use it on code you trust.
FAIL: test_first_page (tests.test_pages.PaginateTest.test_first_page)
AssertionError: Lists differ: [4, 5, 6, 7] != [0, 1, 2, 3]
FAIL: test_last_partial_page (tests.test_pages.PaginateTest.test_last_partial_page)
AssertionError: Lists differ: [] != [8, 9]
Ran 6 tests in 0.001s
FAILED (failures=2)
--- a/shop/pages.py
+++ b/shop/pages.py
@@ -2,7 +2,7 @@ def paginate(items, page, per_page):
"""Return one page of items. Pages are numbered from 1."""
if page < 1 or per_page < 1:
raise ValueError("page and per_page must be at least 1")
- start = page * per_page
+ start = (page - 1) * per_page
return items[start:start + per_page]Our test repo is small on purpose: one off-by-one in paginate(), two failing tests out of six. Real failures can need more context, more turns and more tokens, and some need a person.
An agent fix is many requests, not one: Codex and Droid read the log, open files, patch and rerun the tests, sending the conversation so far each time. In our 6 runs, one fix used between 37,688 and 55,455 tokens as the tools reported them, most of it repeated input the tools marked as cached. Each took 8 to 17 seconds. These figures cover a one-line bug in a six-test repo, not yours.
This page shows plan allowances only; it does not turn our run into a per-fix price. An autofix step that runs for a business is production use, which needs Team or a written agreement. Rates are on Pricing. Run record.
Pick a monthly allowance, or pay per token on Team.
$10/month
$100/month
Pay as you go
Subscriptions are for one person and are not unlimited. New API accounts are reviewed by a person before access turns on, and commercial or production use needs a separate written agreement. Rates and limits are on Pricing.
Not with these files. Both scripts end by writing autofix.patch, the workflow uploads it as an artifact, and the build stays red. A person downloads the patch, reads it and applies it with git apply autofix.patch. Droid at --auto medium is not allowed to git push, and the workflow’s token only has contents: read.
Two things. The prompt says to fix the code and not the tests, and the script does not trust the prompt: git diff --exit-code -- tests exits non-zero if anything under tests/ changed, so no patch is written. In all 6 of our runs the only changed file was shop/pages.py.
With Codex, not if you keep ignore_default_excludes = false: in our check, a command Codex ran could not see UNBIASED_API_KEY with that line and could see it without it. With Droid, it could. Either way your tests and their dependencies run in the same job, so pass the key only to the autofix step and run this on your own code, not on untrusted pull requests.
The action’s inputs include responses-api-endpoint and model, so it may be pointed at https://api.unbiased.ai/v1/responses with model pareto. We have not tested that. The version on this page installs the CLI and uses the same config.toml we ran.
No. Codex has metadata for OpenAI’s own model names and uses fallback metadata for any other name. It printed that warning in every one of our runs, which then fixed the bug and passed the rerun. Format compatibility is not a promise that every Codex or Droid feature works, so tell us if one doesn’t.
Personal includes 50M tokens a week at 20 requests a minute. Our fixes used about 38,000 to 56,000 tokens each, but a bigger repo or a harder failure can use several times that, so measure a few of your own before you count on a number.
Requests over your plan’s limits return an HTTP 429 error. Personal includes 50M tokens a week and 20 requests per minute. Personal Max raises that to 600M a week and 60 requests per minute for $100/month. Team uses prepaid pay-as-you-go credits with no weekly token cap.
No. You send every request to one model string, pareto. Pareto selects frontier and open models for each request and blends their outputs into one answer.
Personal and Personal Max are not our zero data retention tier: prompts and responses may be kept up to 30 days, as section 5(c) of the Terms explains. For work that must not be kept, ask about the Zero Data Retention API on the Private AI page.
Same key, same model string, different job. See every use case. Or find your tool on Works with.
Product names belong to their owners. Unbiased is not affiliated with or endorsed by the makers of the tools on this page.
One config file, one script and one workflow step. Pareto starts at $10 a month for 50M tokens a week.