# My Agent Loop Marked 15 Tasks Done With No Tests Running

> Every gate stage defaulted to an empty string, so 15 tasks logged gate:none and the harness called the run clean. A severity-ladder preflight now blocks a hollow gate stack before task selection.

- Author: Dil Ahdan (https://dil.dev/)
- Published: 2026-10-11
- Updated: 2026-10-11
- Tags: agent-loop, ci, testing, automation
- Canonical: https://dil.dev/notes/agent-loop-hollow-gate-stack/

## Key takeaways

- All four gate stages defaulting to empty strings silently skipped enforcement and logged gate:none instead of failing.
- The gate-honesty banner was warn-only, so a hollow gate stack produced no hard stop and no visible error.
- A severity-ladder preflight now runs before task selection and blocks the run unless --force-gateless or DAPPA_FORCE_GATELESS=1 is set.
- When both test and runtime stages are empty on a TTY the harness prompts y/N; in headless mode it blocks outright.
- Only a missing runtime stage stays a warning; everything else is a hard block.

Fifteen tasks completed, fifteen tasks marked done, and not a single test ran. The harness reported a clean run. It was only afterwards, when failing tests surfaced in the project, that anyone looked at `.dappa/metrics.jsonl` and counted fifteen lines all reading `"gate":"none"`.

## What the Metrics Actually Said

The evidence is straightforward. Running `grep -c` on the metrics file (re-counted 2026-10-09) gives fifteen lines, every one of them with `"gate":"none"`. That is not a partial failure. That is a complete absence of gate enforcement across an entire autonomous run.

The project that produced this file has since had its gate enabled in a follow-up commit. The run itself is the thing worth understanding.

## How the Default Configuration Made This Possible

The dappa config has four gate stages. In the default configuration, all four default to empty strings. That is not a bug in the sense of unexpected behaviour. It is the designed default, and it is the thing that made the hollow run possible.

- **Empty stage, silent skip.** When every stage is an empty string, the enforced gate does not raise an error. It skips.
- **The gate-honesty banner was warn-only.** There was a banner that could flag the situation, but it produced a warning rather than a hard stop.
- **No stage ran, so no stage failed.** The harness had nothing to evaluate, so it recorded completion.

The result is that a correctly configured harness, running a correctly written task list, produced a metrics file that looks like a successful run but contains no evidence of any quality check at all.

## Why the Harness Called It Clean

This is the part that stings a bit. The harness was not lying. It was doing exactly what it was told. No gate stage was configured, no gate stage ran, and the absence of a failure was recorded as a pass. The gate-honesty banner existed precisely to flag this situation, but because it was warn-only it did not stop anything.

The severity of the problem is that the harness was designed to be autonomous. An autonomous loop that silently skips all quality gates and then reports success is not a loop you can trust. The spec that came out of this (spec-01-gate-preflight, added in commit 0954a10 on 2026-07-21) cites nine failing tests shipped as complete. Fifteen tasks, nine failing tests, zero gate runs.

## The Severity Ladder That Now Blocks a Hollow Stack

The preflight introduced in spec 01 (wired in commits f867bd4 and 4ae6b67) runs before task selection. It checks the gate configuration before any work begins. The ladder works like this:

- **All four stages empty.** Hard block. The run does not start unless `--force-gateless` is passed or `DAPPA_FORCE_GATELESS=1` is set in the environment. Both must be supplied per run, not stored in config.
- **Test and runtime both empty, TTY present.** The harness prompts y/N and waits for a human decision.
- **Test and runtime both empty, headless.** No prompt. Hard block. There is no TTY to answer the question, so the run does not proceed.
- **Only runtime empty.** This stays a warning. The reasoning is that runtime checks are often environment-specific and may legitimately be absent in some contexts.

The test for this preflight lives in `scripts/tests/test-gate-preflight.sh` (test 512f112).

## What the Human Found and When

The run completed. The harness reported success. Nobody looked at the metrics file during the run because the harness gave no indication anything was wrong.

The failing tests surfaced afterwards, in the project itself, when work that was supposed to be complete turned out not to be. At that point someone looked at the metrics file and found the fifteen `gate:none` lines. The follow-up commit that enabled the test gate in the dappa config (dfb56b2, 2026-07-13) came after that discovery.

This is the real cost of a warn-only banner on a quality gate. The warning existed. It was not enough.

## What the Preflight Does Not Cover

It is worth being precise about scope. Spec 01 addresses the hollow gate stack case specifically. It does not claim to fix every way a gate can be misconfigured or bypassed.

- **A gate that runs but tests nothing meaningful** is not caught by this preflight.
- **A gate that is configured but broken** will still run and may still report incorrectly.
- **The `--force-gateless` escape hatch** exists deliberately, for cases where a gateless run is genuinely intentional. It is not audited beyond the per-run requirement.

The harness is not claimed to be fully fixed beyond the scope of spec 01. The preflight closes one specific gap: a run that would otherwise start with no gates at all now cannot start silently.

## What I'd do differently

- **Make the gate-honesty banner a hard block from day one.** A warn-only banner on a quality gate is not a safety mechanism. It is a note that gets ignored.
- **Require explicit opt-in for a gateless run, not just a default.** The default of empty strings is convenient for getting started but dangerous in an autonomous context. A prompt or a flag should be required from the first run, not retrofitted after a failure.
- **Add a preflight summary to every run log.** Even when the gate is configured correctly, the run log should show what stages were checked before task selection began. That makes a hollow stack visible without needing to grep the metrics file after the fact.
- **Treat headless and TTY contexts differently from the start.** The distinction between prompting on a TTY and blocking headless is the right behaviour. It should have been the default, not something added in a spec written after fifteen tasks ran without a single test.

## Quick answers

**What does gate:none mean in dappa metrics?**

It means the task completed without any gate stage running. All four gate stages were empty strings, so enforcement was silently skipped and the result was recorded as gate:none rather than as a pass or fail against real checks.

**How did 15 tasks pass if no tests ran?**

The harness treated an empty gate stack as a clean run because the gate-honesty banner was warn-only and the enforced gate silently skipped when every stage was empty. There was nothing to fail, so nothing failed.

**What is the severity-ladder preflight?**

It is a check that runs before task selection. If all four gate stages are empty it blocks the run entirely unless --force-gateless or DAPPA_FORCE_GATELESS=1 is supplied per run. If only the runtime stage is empty it stays a warning rather than a block.

**Does the preflight fix work in headless CI?**

Yes. On a TTY with both test and runtime stages empty the harness prompts y/N. In headless mode, where there is no TTY, it blocks outright without prompting.

**How was the hollow gate stack discovered?**

It was found by a human after the run completed, when tests that should have been run by the gate were failing in the project. The harness itself gave no indication anything was wrong during the run.

**Is the harness fully fixed after spec 01?**

Spec 01 and its preflight cover the hollow gate stack case specifically. The harness is not claimed to be fully fixed beyond that scope.
