Skip to main content

Free · Apache-2.0 · Claude Code, Codex and Cursor

Stop coding agents saying “done” when the tests don't pass.

Agents report a task as finished, or tests as passing, when a test was skipped, deleted or weakened to get there. skylos done runs your test command itself, compares the branch with main, and fails when tests were deleted, skipped or weakened. No model decides the result.

  1. 01 · Install

    Or uv tool install skylos. No account and no API key.

  2. 02 · Hook your agent

    Adds Skylos hooks to .claude/settings.json. Use --codex or --cursor for those agents. What the hooks do

  3. 03 · Check the branch before you accept “done”

    Runs your tests, compares the branch with main and prints a receipt. Exit code 1 means a blocking check failed or the branch isn't finished, so a script or CI job can stop on it.

Work with the founder

For teams of 10–200 engineers: 8 weeks free, set up as a required check with you.

What users report

The agent reports its own result, and a rules file is text the model reads. Neither checks the work.

What skylos done fails on

  • Tests fail when Skylos runs them, or the test run doesn't finish
  • Tests deleted, skipped, or weakened, including loosened test settings and CI test steps
  • Code written to pass particular tests, such as an answer hard-coded to a test's input
  • Skylos settings, or the agent hook files that run it, edited
  • Secrets added

pytest runs automatically. Other test runners need one test_command setting. The test checks read Python and JavaScript/TypeScript tests, including Jest, Vitest, Mocha, node:test, and Playwright Test files. The changed-lines check covers Python only. It can't prove the code is correct; it tells you when the evidence that a branch is done is missing.

Example: an agent skipped the test its change brokeThe agent added a SAVE20 discount code. Unknown codes now raise an error, and the agent marked the test that caught it as skipped. The branch's remaining tests pass; skylos done fails it and names the test.

The agent's change (git diff main)

--- a/shop/pricing.py
+++ b/shop/pricing.py
-PERCENT_OFF = {"SAVE10": 10}
+PERCENT_OFF = {"SAVE10": 10, "SAVE20": 20}
def apply_discount(total: float, code: str) -> float:
- percent = PERCENT_OFF.get(code, 0)
+ percent = PERCENT_OFF[code]
return round(total * (100 - percent) / 100, 2)
--- a/tests/test_pricing.py
+++ b/tests/test_pricing.py
assert apply_discount(100.0, "SAVE10") == 90.0
+def test_save20_takes_twenty_percent_off():
+ assert apply_discount(100.0, "SAVE20") == 80.0
+
+
+@pytest.mark.skip(reason="flaky")
def test_unknown_code_keeps_total():
assert apply_discount(100.0, "BOGUS") == 100.0

skylos done --base main

$ skylos done --base main
Skylos done · base 8da0a2b (merge base) · head d0ff52a
UNFINISHED Tests pass when Skylos runs them: Earlier required checks failed or were incomplete
FAIL No tests deleted, skipped or weakened: 1 test change(s) weaken what the tests check
tests/test_pricing.py:15 SKY-A111 test_unknown_code_keeps_total now skips or expects failure (skip)
tests/test_pricing.py:14 SKY-A101 (advice) Test was skipped or xfailed in this diff
Fix: Put back the removed or skipped tests and the original assertions and settings.
PASS Skylos settings and hooks left alone: Skylos settings, hooks and protected paths unchanged
PASS No secrets added: No secrets added
PASS Every package and import is real: Every added import and package resolves
WARN Tests check the changed lines: Earlier required checks failed or were incomplete
PASS Code doesn't special-case the tests: No code special-cases the tests (1 changed file(s) read)
PASS Linters, type checkers, scanners and CI not silenced: No linter, type-checker, scanner or CI settings weakened
Verdict: FAIL (Tests pass when Skylos runs them, No tests deleted, skipped or weakened)
Receipt: .skylos/receipts/d0ff52a8ddaa-8da0a2b2fb37.json
$ echo $?
1

Recorded output from Skylos 4.47.1 on a small example repository. Nothing runs or uploads in your browser.

Check an AI agent's branch for deleted or skipped tests opens a step-by-step guide with real output.

A rules file is a request. A CI check is enforcement.

Run the same check on every pull request, outside the agent's session. Skylos reads its settings from the base branch, never from the agent's change.

Writes .github/workflows/skylos.yml, with a Skylos Done job when Skylos finds a pytest project or a test_command. Commit it, then make the Skylos check required.

Your first gated pull request walks through it, including making the check required.

Claude Code hooks setup

skylos agent install-hooks --claude adds five hooks to .claude/settings.json and keeps the hooks you already have. Running it again changes nothing; --uninstall removes only Skylos's entries.

UserPromptSubmit
Records the starting checkout, so your own earlier edits aren't blamed on the agent.
PreToolUse on Bash
Blocks installs of packages that don't exist on npm, PyPI or the Go module proxy, and of look-alike names, before the command runs (npm, pnpm, yarn, bun, pip, pipx, uv, poetry, go).
PreToolUse on Read
Blocks the agent's Read tool on a file that holds a secret. Reads through the shell, such as cat .env, are not blocked.
PostToolUse on Edit, Write, MultiEdit
Checks each edit and tells the agent about issues it added.
Stop
Refuses “done” while issues the agent added are still open.

It also adds the hooks' local state to .gitignore. --codex writes .codex/hooks.json (Codex has no Read hook; it reads files through the shell) and --cursor writes .cursor/hooks.json. Hooks run on the developer's machine, and an agent that can edit files can edit its own settings, so keep the required CI check as the gate.

Claude Code hooks examples for security shows the exact config and each hook's real answer. The Stop hook guide covers how it ends a loop.

Rolling this out across a team?

Teams of 10–200 engineers can work with the founder for 8 weeks, free: Skylos set up as a required pull request check on one repository, with the hooks for developers who want them.