# Seeing what happened when your code ran

> Opens a finished run and shows what it did — each step named and pointed at its source line, the values that crossed between client and server, and, when it threw, the exact place and the values in scope there. You do not have to have been recording, and you do not have to reproduce the failure.

<!-- id: local-seeing-what-happened · area: local · stability: stable · html: https://osysharp.com/reference/local/seeing-what-happened/ -->

## Summary        {#summary}

Answers the question you actually have when something goes wrong: **what happened when it ran?** A run that threw is
retained with the stack and the values as they were **at the moment of the throw** — so instead of adding logging and
running the failure a second time, you open the failure you already have.

## Signature      {#signature}

```console
osy inspect [traceId] [--list] [--fault] [--step N] [--correlation-id <id>] [--json]
```

## Description    {#description}

Something failed — a test went red, a page action broke, a tool call came back with an error. The message tells you
*that* it threw. It does not tell you **why**, because the message does not carry the values.

`osy inspect` does. Run it with no arguments and it lists the runs it has kept, the faulted ones marked with the
exception that ended them. Give it a run and it opens that run.

**Every step is named and points at your source.** A step is not an opaque position — it shows the function it ran, the
file and line it was on, and the statement itself. A step lands where you would put your cursor, in the `.osy` you wrote.

**A run that crosses client and server reads as one story.** A page action that awaits a server function — or a
server function that calls back to the client — interleaves both sides in one sequence, and shows the values that
**crossed** between them: the arguments handed over (`→ handoff`) and the value handed back (`← resume`). "Why did the
server receive the wrong argument?" and "why did the page get the wrong result?" become answerable without guessing at
the boundary.

**You did not have to predict the failure.** A fault is captured whether or not you had recording on: it costs nothing
until something actually throws, so the moment you most need the state is never the moment it is discarded. Turning
recording on (see [Recording a run](https://osysharp.com/reference/local/recording-a-run/)) additionally records the *steps that led there* — but you never need it
just to see where and why a run died.

**The values are the callee's.** A throw is almost never in the function you invoked; it is several calls down, and the
state that explains it belongs to the function that threw — the arguments it was called with, and the local that made
the guard trip. `--fault` shows that stack, innermost frames included.

**For a wrong result rather than a crash, record with `--full`.** A fault shows the values at the throw; a run that
finished with the *wrong answer* never threw. Record it with `osy trace start --full` ([Recording a run](https://osysharp.com/reference/local/recording-a-run/)) and
each step carries the locals in scope at that point — so you watch the value that ends up wrong take shape, step by
step, on both sides of the run.

**Nothing is resolved for you.** A reference to a record is shown as its type and id, never fetched — reading it would
mean a database query under somebody's permissions, and a post-mortem view must not do that. A string is shown quoted,
so `"3"` is visibly not `3`.

**It is local only.** Traces are kept in the local platform you run while developing ([Running a local platform](https://osysharp.com/reference/local/running-a-local-platform/))
and never leave your machine. A deployed server does not record them and will tell you so if asked.

Traces are scoped to *this* project's app, so a workspace of several apps does not mix their runs. Only the most recent
runs are kept; a faulted run is kept in preference to a successful one, so a failure is not pushed out by the green runs
behind it. If a run was long enough to be truncated, it says so rather than presenting a partial story as a whole one.

**You do not need the run's own id to find it.** When something failed through your app — an error dialog, a log line —
you usually hold a **correlation id**, the id of the interaction, not of one run. `--correlation-id <id>` opens the set
of runs that one interaction caused (a single click can trigger several), so you go from "this failed" to "these are
the runs it produced" without hunting for a trace id.

`--json` writes the trace as JSON — the form to hand to a coding agent, with the names, source spans, crossed-wire
values and locals it needs to act against your source.

This is a **read** of a finished run. To pause a *live* one and step it, use the debugger instead.

## Examples       {#examples}

```console
osy inspect                          # what runs are retained? which one broke?
osy inspect <traceId> --fault        # where it threw, and the values that were in scope there
osy inspect <traceId>                # the whole run
osy inspect <traceId> --step 12      # one recorded step
osy inspect --correlation-id c-9f2a  # the runs one interaction (a click) caused
osy inspect <traceId> --json         # the same, as JSON
```

A test fails. The message says only `ValidationException: discount must be under 100%`. The fault says why — named, at
the source line, with the values in scope:

```console
$ osy inspect 232a6a2c --fault
FullDiscount_IsRejected  Faulted  232a6a2c-ec59-402a-ab53-7d6747687c4b

ValidationException: discount must be under 100%

  ApplyDiscount  at pricing.osy:14
    total = 200
    percent = 100
    factor = 0
```

`percent` came in as `100`, so `factor` computed to `0` and the guard tripped. Nobody was recording.

A checkout returns the wrong total — no crash, just a wrong number. Recorded with `--full`, the whole run reads as one
story: each step at its source line, the values crossing between the page and the server, and the locals taking shape:

```console
$ osy trace start --full
$ osy inspect 7c1d0a4e
Cart.CheckOut  Completed  7c1d0a4e-...

    0  client stmt    CheckOut  at checkout.osy:8   total = Price(cart);
    1  → handoff  Price(cart: [Item#a1, Item#b2])
    2  server stmt    Price     at pricing.osy:4    var sum = 0;
         sum = 0
    3  server return  Price     at pricing.osy:9    return sum;
         sum = 240
    4  ← resume   Price returned 240
```

The arguments that crossed to the server, the value that came back, and `sum` at each step — enough to see exactly
where 240 should have been something else.

## See also       {#see-also}

[The inner loop](https://osysharp.com/reference/local/the-inner-loop/) — where `osy inspect` sits in the develop-and-debug loop.

[Recording a run](https://osysharp.com/reference/local/recording-a-run/) — also record the steps that led to a failure.

[Log.*](https://osysharp.com/reference/diagnostics/log/) — what your app writes with `Log.*`; a failure's log line names the run to inspect.

[Understanding your app](https://osysharp.com/reference/local/understanding-your-app/) — what the app *is*, as opposed to what it did.
