Skip to content

Reading an investigation

A worked example, followed by the signals that separate a result you can act on from one that merely sounds right.

The case

A CI run on break/subtle failed at Run tests. One test out of 403 failed. The diff since the last green run touched two files: a four-line change to app/auth.py and an eighty-seven-line rewrite of app/rate_limit.py.

This is the shape that defeats a script. The obvious suspect is the large hunk. The traceback points at a test file, which is neither of the changed files. Nothing in the log names the actual cause.

What the agent did

investigation trace
⌕  Looking up KUNDAN1334/traceme-lab   finding the most recent failed run
◉  Workflow `CI` failed                 run 15938201234
◎  First failing step: Run tests         log window anchored on the first real error
±  Diffed last green -> failing          2 file(s) changed across 1 commit(s)
▤  Opened app/auth.py                    chosen by the agent
✓  Evidence collected

One tool call. The agent read the traceback, saw an AttributeError on a value the test did not create, and went to the file that creates it — not to the biggest hunk in the diff. That decision is the entire product.

The evidence

  • logE AttributeError: 'dict' object has no attribute 'expires_at'
  • test_auth.py:47tests/unit/test_auth.py:47: AttributeError
  • auth.py:18app/auth.py:18: return {'value': fresh.value, 'user': fresh.user, 'expires_at': fresh.expires_at}
  • log1 failed, 402 passed in 12.41s

Read them as an argument. Line one is the error. Line two is where it surfaced — in the test, at line 47. Line three is the cause, in a different file, at line 18: a function returning a dict. Line four bounds the scope: one failure out of 403, so this is not an environment collapse.

Every line is quoted. You can open the run and the file and confirm all four in under a minute, which is the standard the evidence field exists to meet.

The conclusion

app/auth.py:refresh() was changed to return a plain dict instead of the Token it is still annotated to return. tests/unit/test_auth.py:47 reads .expires_at off that result and raises AttributeError. The token-bucket rewrite of app/rate_limit.py in the same commit is the largest hunk in the diff but is not imported anywhere in the test suite.

Note the last sentence. The agent explicitly discharges the large hunk rather than ignoring it. That is worth more than it looks: it tells you the alternative was considered, so you do not have to go and check it yourself.

suggested patch
def refresh(token: Token, now: int) -> Token:
if is_expired(token, now):
raise AuthError("cannot refresh an expired token")
- fresh = issue_token(token.user, now)
- return {"value": fresh.value, "user": fresh.user, "expires_at": fresh.expires_at}
+ return issue_token(token.user, now)

Confidence was 9. Nothing in the root cause is absent from the evidence, so that is the correct score.

Signals of a strong result

  1. 1

    The tool calls are few and targeted

    Zero to two calls, each one aimed at something the log named. A single read_file on the file in the traceback is the healthiest pattern there is.

  2. 2

    The evidence chain is complete without the prose

    Error, location, cause, scope. If you can reconstruct the argument from the quoted lines alone, the prose is a summary rather than a substitute.

  3. 3

    The root cause names things

    A file, a function, a value. “A recent change to the authentication logic” is not a root cause; app/auth.py:refresh() returning a dict is.

  4. 4

    Alternatives are addressed

    When the diff contained an obvious decoy, a good diagnosis says why it is not the cause.

Signals of a weak result

  • All six tool calls used. Usually an agent that could not find the thread and kept pulling. Read the diagnosis with the confidence score firmly in mind.
  • Several vague search_code calls. A sign it did not know what it was looking for. Targeted searches for a symbol the log named are fine; searches for general terms are fishing.
  • Evidence that restates the conclusion. If every quoted line is the error message in a slightly different form, nothing has been established beyond “something failed”.
  • High confidence with a hedged root cause. “Likely caused by” at confidence 9 is internally inconsistent, and the confidence is the field that is wrong.
  • The category contradicts the failing step without saying why. A config diagnosis for a failure at Run tests can be exactly right, but it needs a sentence explaining the connection.

When something looks wrong, suspect the inputs first

Open the full record and read the log window. An anchor that landed on a warning instead of the real error, or a baseline commit further back than you expected, explains most bad diagnoses — and both are visible at a glance.

This example is available to replay in full — watch it run.