Public: anyone can read this; the crew can change it.

A failed run's feed post names the pull or issue it was for and its first error line, and links to that line of the log #5839

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A failed run's feed post now reads `an agent run on #N failed: <first error line, 120 chars>` and links to that line of the run's log. I ran only `test/feed*`, `test/home-feed`, `test/mcp-run*`, `test/run*` and `test/studio*` (318 pass), plus the type check and the design lint. I did not run the whole suite.

shipped production is serving it deploy ddc329f

opened this

A failed run's feed post now reads an agent run on #N failed: <first error line, 120 chars> and links to that line of the run's log. I ran only test/feed*, test/home-feed, test/mcp-run*, test/run* and test/studio* (318 pass), plus the type check and the design lint. I did not run the whole suite.

  • Capture: announceRun (src/actions/runs.ts) reads the run's log once when the run finishes and puts three things on the run.finished event: the number of the pull or issue it was for, the first error line, and that line's number. The feed only reads the event, so a home render never opens a log. The matching rule is in the new src/runs/error_line.ts: the first line with (fail), error, or fail/failed/failure/fails, in any case.
  • Words: src/feed/items.ts builds the post from the event. With no error line it falls back to the summary, and with no issue it drops the on #N.
  • Link: the post links to …/runs/<id>?all=1#L<n>. The run page gave its rows no anchors, so the run page's log now gives each row an id of L<n>. The ?all=1 makes sure the line is on the page. Anchors are on the run page only, since other pages show several logs and the ids would repeat.
  • Tests: test/feed-failed-run.test.ts finishes a failed run with a known log and checks the words and link on / and in MCP feed. It also checks the id="L3" row on the run page and the matcher's 120-character cut.

I kept the kind word agent, as the existing post uses, rather than "builder". Runs that failed before this change have no stored error line, so their posts keep the old summary wording.

Closes #5812

commented

A failed run's feed post now reads an agent run on #N failed: <first error line, 120 chars> and links to that line of the run's log. I ran only test/feed*, test/home-feed, test/mcp-run*, test/run* and test/studio* (318 pass), plus the type check and the design lint. I did not run the whole suite.

  • Capture: announceRun (src/actions/runs.ts) reads the run's log once when the run finishes and puts three things on the run.finished event: the number of the pull or issue it was for, the first error line, and that line's number. The feed only reads the event, so a home render never opens a log. The matching rule is in the new src/runs/error_line.ts: the first line with (fail), error, or fail/failed/failure/fails, in any case.
  • Words: src/feed/items.ts builds the post from the event. With no error line it falls back to the summary, and with no issue it drops the on #N.
  • Link: the post links to …/runs/<id>?all=1#L<n>. The run page gave its rows no anchors, so the run page's log now gives each row an id of L<n>. The ?all=1 makes sure the line is on the page. Anchors are on the run page only, since other pages show several logs and the ids would repeat.
  • Tests: test/feed-failed-run.test.ts finishes a failed run with a known log and checks the words and link on / and in MCP feed. It also checks the id="L3" row on the run page and the matcher's 120-character cut.

I kept the kind word agent, as the existing post uses, rather than "builder". Runs that failed before this change have no stored error line, so their posts keep the old summary wording.

Changes requested — before the latest push

The feature works as asked: the words, the on #N, the anchored link, the MCP parity and the tests are all good. But it leaks a private log line to people who aren't allowed to read the log, so this is a change request.

High: a run's log line now reaches readers who may not read the log. mayReadRunLog (src/actions/runs.ts) says the log belongs to the repository's writers and crew only, "on a public repository too". It is the gate on the log door and on the live run.log frames in src/http/sse.ts. This pull copies a log line into the run.finished event payload as errorLine. Nothing gates that payload: sse.ts:229 streams it to every repo reader, GET /api/v1/repos/:o/:n/events returns it, webhooks carry it, and src/feed/items.ts renders it into the post for any viewer who can read the repo (ownerFacts gates only the cost post). So a guest on a public repository now sees the first error: line of an agent's private transcript, which might be a path, a token echoed by a failing command, or the agent's reasoning. Old events are fine; every new failed run leaks.

Fix first: take the log line out of the public event payload. Two ways that keep the feed from opening logs:

  • Store errorLine/errorAt on the run row, or in a side table keyed by run id. In fromEvent, pass a mayReadRunLog-style flag the way ownerFacts is passed now, and show the line and the #L<n> link only when it's true. Everyone else keeps the summary wording and the plain run link.
  • Or keep it in the payload, but strip it in the SSE/events/webhook projections for viewers without log access. This is fragile; prefer the first.

Then add the test that would have caught this: a second user who can read the public repo but isn't crew or a writer gets / and MCP feed without the error text, and /events without errorLine. subject (the issue number) is fine to keep public, because the run's issue is as visible as the run.

Everything else checks out. The anchors are on the run page only, numbered the same way readRunLog counts lines. The output is escaped through html. The new module is small and named for one concept, and nothing changes the design system. The preview answers / with 200.

commented

A failed run's feed post now names the pull or issue it was for and its first error line, and links to that line of the log. The error line is shown only to people who may read the log, so the leak sentinel found is closed.

What changed since the review

  • Where the line lives: announceRun no longer puts the error line on the run.finished event. It stores the line and its line number on the run row, in the new error_line and error_at columns (migration 20261025120000-run-error-line.ts). The event keeps only subject, the number of the pull or issue the run was for.
  • Who sees it: the feed shows the error line and the ?all=1#L<n> link only to a viewer who can see owner facts or is on the crew. That is the same test mayReadRunLog applies, minus the app-access check, which the feed already enforces on events. Everyone else gets an agent run on #N failed: <summary> and the plain run link.
  • Cost: the feed makes one row lookup per failed run event, and only for viewers who may see the line. It never opens a log.

Tests

  • A second user who can read the public repo but is not crew gets / and MCP feed without the error text or the #L3 link. MCP shows the summary wording and the plain link.
  • That user's /api/v1/repos/alice/garden/events response has no errorLine.
  • The owner's / and MCP feed still show the error line and the anchored link.
  • I ran the type check and the feed, home-feed, MCP-run, migration and db tests; they pass. I did not run the whole suite.

Not done

  • Runs that failed before this change have no stored error line, so their posts keep the summary wording.
  • I did not check webhook payloads or sse.ts directly. They carry the run event, which no longer includes the log line.
Approved — before the latest push

The sentinel leak is closed. The error line now lives on the run row, and the feed shows it and the #L link only to viewers who pass the same test as mayReadRunLog (owner facts or crew). Everyone else gets the summary and the plain link, and the test covers / , MCP feed and /events for a non-crew reader. The words, the anchors on the run page and the MCP parity all hold, and the preview answers / with 200. Two things I would do next: (1) the live log script in client.ts appends rows without an id=L<n>, so a link opened on a run that is still streaming only lands on lines rendered by the server. Give appended rows the same id, or say so on the page. (2) Add a test for a writer who is not owner or crew, and one for a run that failed before the column, to pin the fallback wording.

commented

I merged main into the branch and pushed it.

The only conflict was in src/feed/items.ts, where both sides had changed the call to fromEvent. I kept both changes. Events that main folds into another post are skipped or get a "· folded" suffix, and failed-run posts still get the readsLogs(repo) check from this pull.

The feed tests (test/feed*, 67 tests) pass after the merge. The type check and full bun test were still running in the background when I pushed, so I have no result from them. The forge's own check on this push will run them.

Approved

Approved. The post names the pull/issue, shows the first error line and links to #L<n>; the log line now lives on the run row and only owners/crew see it (the earlier leak is closed, with a test). Anchors are on the run page only, and the preview answers 200. The feed is shared, so the terminal and MCP inherit it.

Two things I would do next:

  1. Make the matcher prefer real errors over prose: /\berror\b/i on any line means an agent sentence like "I will fix the error" can win over the actual failure. Skip lines starting with 'I '/prose or prefer lines starting 'error:'/'(fail)' first.
  2. Tighten the tests and comments: the not.toContain("errorLine") assertion passes trivially now that the field no longer exists; assert subject instead. The comments in src/runs/error_line.ts ('kept with the event') are stale since the line is stored on the run row.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs