# A successful response can describe the wrong operation

By ayobamih

Canonical: https://aiworkaccountability.com/writing/a-successful-response-can-describe-the-wrong-operation/
Published: 2026-10-26
Updated: 2026-10-26

An endpoint can respond successfully without honouring the operation its caller requested. A page can exist at the expected URL, return HTTP 200 and carry the expected metadata, while displaying the wrong content.

I encountered both problems in Agent Shop, my catalogue and discovery application. The repairs were small. The more useful lesson was the difference between the properties the existing checks established and the outcome someone might infer from them.

This article reconstructs two October 2026 changes from pinned source, pull-request diffs and historical GitHub Actions logs retrieved on 26 October. It is a retrospective, not a fresh reproduction or an assessment of the application's current production state. The [evidence package](/evidence/semantic-success/README.md) preserves the claim map, CI observations and selected diagnostics.

## Two different ways to pass the wrong check

| Case | Coarse observation | Missing property | Repair and acceptance |
| --- | --- | --- | --- |
| Read-only discovery endpoint | The server responds; supported GET requests work | An unsupported write method must receive an explicit rejection | Four method handlers; assertions for 405 and `Allow: GET` across three paths |
| Nested completion page | The URL returns 200 and includes `noindex` | The intended child content is actually visible | A child outlet; browser assertions for the order-help heading and contact link |

These are two mechanisms in one application. They are not a representative sample of AI-generated defects, and the twelve method/path checks below are not twelve separate incidents. Their value is that the gap between a check and the intended behaviour is concrete enough to inspect.

## Case one: a GET handler did not reject other methods

The discovery endpoints return catalogue metadata and integration briefs. Retrieving a brief is not an instruction to execute a capability or submit a request. The route therefore needs an explicit boundary between reading a representation and attempting an unsupported operation.

At the [failing CI revision](https://github.com/AyobamiH/agent-shop-products/blob/37f2ff6ab25764abdb46d62f0d709648a128f5ca/frontend/src/routes/capability-brief%5B.%5Djson.ts), `/capability-brief.json` declared a GET handler. A recognised capability ID produced a JSON brief; a missing ID produced a JSON 404. No write-method rejection was declared there.

The [dated validation report](https://github.com/AyobamiH/agent-shop-products/blob/3836d8ccdda1251dc26922794bb61c944a3a4a83/docs/REGISTRY_VALIDATION_2026-10-09.md) records that an unsupported POST fell through to the application renderer and returned HTML with HTTP 200. This is an observation about that application revision and configuration, not a claim about every TanStack application or current framework version.

The recovered [failed CI job](https://github.com/AyobamiH/agent-shop-products/actions/runs/37901366579/job/113724456599) supplies narrower, direct execution evidence. Its 42 unit tests passed. The subsequent browser suite failed at this assertion:

```python
assert (await page.request.post(
    ORIGIN + "/capability-brief.json", data="{}"
)).status >= 400
```

The traceback confirms that the expected rejection did not occur. It does not print the exact status or response body; the specific HTML/200 diagnosis comes from the dated report. Keeping those sources distinct prevents a useful historical account from acquiring evidence it never recorded.

A client that treats any successful HTTP status as acceptance could misdescribe this result. That is a plausible downstream risk, not evidence that a write occurred or that an agent actually made that mistake in this incident. A JSON-parsing client might instead fail when it encounters HTML. Both observations point back to the same server contract: the unsupported request should receive a deliberate answer.

## Make the refusal part of the route

The repair introduced a [shared rejection helper](https://github.com/AyobamiH/agent-shop-products/blob/b8e1b09985c6e755d200e92f6cff3224b5be0907/frontend/src/lib/machine-readable/read-only.ts). Its core is:

```js
function rejectWrite() {
  return Response.json(
    { error: "Read-only discovery surface; use GET" },
    { status: 405, headers: { allow: "GET" } },
  );
}

export const readOnlyWriteHandlers = {
  POST: rejectWrite,
  PUT: rejectWrite,
  PATCH: rejectWrite,
  DELETE: rejectWrite,
};
```

The [brief route](https://github.com/AyobamiH/agent-shop-products/blob/b8e1b09985c6e755d200e92f6cff3224b5be0907/frontend/src/routes/capability-brief%5B.%5Djson.ts) spreads these handlers alongside its GET implementation. The explicit refusal stops the missing-handler path from deciding the response accidentally.

The choice of 405 has a defined meaning. [RFC 9110, section 15.5.6](https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.6) describes a method recognised by the server but unsupported by the resource, and requires an `Allow` header listing supported methods. The repair implements the application's stated GET-only boundary. The historical acceptance covered four write methods; it was not a complete HTTP conformance assessment, including HEAD and OPTIONS.

The [revised browser test](https://github.com/AyobamiH/agent-shop-products/blob/b8e1b09985c6e755d200e92f6cff3224b5be0907/frontend/e2e/capability_registry.py) checks POST, PUT, PATCH and DELETE against `/capabilities.json`, `/capability-brief.json` and `/coding-bugs.json`. For each of the twelve combinations it asserts exactly 405 and `allow == "GET"`. Earlier checks in the same suite exercise supported reads and missing-ID behaviour, so rejection is not the only behaviour under examination.

There is a useful limit here: the rejection loop does not assert the response's content type or parse its error body. `Response.json` expresses the implementation's intent, but a future outside-in check should also verify the machine-readable representation if clients depend on it. That is a recommendation from this review, not an assertion that the historical test already made.

The final PR run's [browser job passed](https://github.com/AyobamiH/agent-shop-products/actions/runs/37903710233/job/113731992347), and [PR #26](https://github.com/AyobamiH/agent-shop-products/pull/26) merged on 9 October. This sequence supports the recorded repair at its tested revision. It does not establish that every later deployment retained the behaviour.

## Case two: the child route existed, but its content did not render

The second defect involved `/private-kits/complete`, a child of the private-kit catalogue route. The [historical report](https://github.com/AyobamiH/agent-shop-products/blob/910ea7b1e1a9ada679b5ddb698dde86e22bd853d/docs/CUSTOMER_CONTACT_VERIFICATION_20261012.md) records that visiting the completion URL displayed the parent listing instead of order help.

The [parent before the repair](https://github.com/AyobamiH/agent-shop-products/blob/717df27c708b5de31b6afba228a586ef7139dd5d/frontend/src/routes/private-kits.tsx) directly selected `PrivateKitsPage` as its component. It did not render an outlet for the matched child. A matching route and its metadata were therefore insufficient to establish that the child UI was present.

The test gap is visible in the [earlier browser script](https://github.com/AyobamiH/agent-shop-products/blob/717df27c708b5de31b6afba228a586ef7139dd5d/frontend/e2e/private_kit_commerce.py). Its completion-page checks fetched the URL, asserted HTTP 200, and searched the response for `noindex`. Those are useful checks for availability and indexing instructions. Neither asks whether the visitor can see the order-help page.

The repair adds a small layout decision:

```tsx
function PrivateKitsLayout() {
  const matchRoute = useMatchRoute();
  return matchRoute({ to: "/private-kits/complete" })
    ? <Outlet />
    : <PrivateKitsPage />;
}
```

This is the [actual merged layout logic](https://github.com/AyobamiH/agent-shop-products/blob/910ea7b1e1a9ada679b5ddb698dde86e22bd853d/frontend/src/routes/private-kits.tsx). It renders the child for that completion match and preserves the catalogue otherwise. The [TanStack outlet documentation](https://tanstack.com/router/latest/docs/framework/react/guide/outlets) explains the mechanism: an outlet renders the next matching child. This case is an application composition error; it is not evidence of a router defect.

The [updated acceptance script](https://github.com/AyobamiH/agent-shop-products/blob/910ea7b1e1a9ada679b5ddb698dde86e22bd853d/frontend/e2e/private_kit_commerce.py) keeps the status and `noindex` checks, then navigates a browser to the completion URL. It requires the exact visible heading “Your kit order” and the customer-contact link within `main`. A separate [deployed-surface checker](https://github.com/AyobamiH/agent-shop-products/blob/910ea7b1e1a9ada679b5ddb698dde86e22bd853d/frontend/scripts/verify-deployed-surface.mjs) also requires the order-help text in the response.

The inspected browser fixture uses a 390 × 844 viewport. The merged revision's [CI browser step passed on 12 October](https://github.com/AyobamiH/agent-shop-products/actions/runs/38295667457/job/114966012314). The initial wrong-content observation is supported by the report and before-source; I did not recover a separate failing pre-repair browser trace for this case. The PR also records a historical production readback, which remains an author-reported deployment result here.

Rendering order help is only one boundary. It does not prove a payment was accepted, an entitlement exists, a download was delivered, or a customer completed recovery. Those require their own evidence. The source keeps order-status and access checks separate; the article makes no claim of customer loss or completed live sales.

## The evidence needs its own contract

The retrospective exposed two details worth keeping in the public record.

First, GitHub's PR run metadata identifies a head commit, while the checkout log can identify a different, synthetic merge revision. The failing method run reports head `11e1c688…` but checked out `37f2ff6a…`. The successful run reports head `701a0b26…` but checked out `b8e1b099…`. The completion run was a push build and checked out the actual merge `910ea7b1…`. The [machine-readable record](/evidence/semantic-success/case.json) retains the full identifiers, and this article links the method sources at the tested merge revisions.

Second, a passing log banner can describe its assertions imprecisely. The completion script prints “no price”, although an earlier assertion explicitly expects the planned £9 price to be visible. The API checks separately ensure that active price fields are absent from the unavailable offer. The banner is not a substitute for reading those assertions. This article therefore does not turn that phrase into a claim that the page contained no price.

Neither detail invalidates the route repair. Both affect how precisely someone can describe what the historical run established.

## A practical review habit

For an AI-assisted task, write down the requested outcome before interpreting the final result. Then locate the observation that can distinguish that outcome from a plausible wrong result.

For these cases, the useful questions were specific: did an unsupported method receive the intended refusal, and did the completion URL render its own content? A successful GET elsewhere, a green unit suite or the presence of page metadata could not answer them.

A practical acceptance record can name the operation, the relevant revision and environment, the observable result, and the remaining untested properties. This is ordinary contract and integration testing applied carefully. These examples do not establish a new testing method or measure how often AI-assisted work fails.

They extend the question explored in [When an agent tool reports the wrong success](/writing/when-agent-tools-report-the-wrong-success/): does the reported result correspond to the action the caller actually asked for? For this application, making the answer inspectable required explicit refusals, rendered-content assertions, and a record that preserved the failed attempt.

## Contribution, evidence and limits

I am the owner of Agent Shop and the author associated with both pull requests as `ayobamih`. The work was developed with AI coding assistance. AI also assisted this retrospective's retrieval, source comparison and drafting; it is not an independent audit. The record does not attribute every line to a human or a model, and it does not establish that AI caused either defect.

The public package contains [claims and CI metadata](/evidence/semantic-success/case.json), [selected historical log lines](/evidence/semantic-success/ci-excerpts.txt), [source references and hashes](/evidence/semantic-success/sources.json), a [citation](/evidence/semantic-success/citation.bib) and [checksums](/evidence/semantic-success/SHA256SUMS.txt). It excludes private working records and unrelated account details. Hashes support byte comparison; they are not a third-party attestation of the conclusions.

No application replay, payment, provider write or fresh Agent Shop production acceptance was performed for this article. The evidence supports two scoped historical repairs and the engineering reasoning above. Broader effectiveness, production impact and generalisation would require a separately designed study.


Author-published case study by Ayobami John Haastrup (ayobamih). Historical source and CI review, with AI assistance. Not peer-reviewed; current Agent Shop production behaviour was not retested for this article.
