Skip to content

Sonny: what failed in the background is a red ✗ in the footer and first in /background - #419

Merged
lydakis merged 8 commits into
mainfrom
claude/project-thread-u2fp8q
Oct 30, 2026
Merged

lydakis merged 8 commits into
mainfrom
claude/project-thread-u2fp8q

Conversation

@lydakis

@lydakis lydakis commented Oct 30, 2026

Copy link
Copy Markdown
Owner

🤖 Generated with Claude Code

https://claude.ai/code/session_013jkdGEbQZnXUy1kATtYcQ6

Why

A nightly run that failed while the laptop was offline or a login had expired failed without anyone seeing it. The trigger recorded sent, and the turn it sent failed afterwards. George picked a status-bar line so that failed background work reaches him. He also asked that an agent be able to see the same failure he sees.

What

  • How sent turns ended. Every ten seconds, the window asks the daemon how each trigger's last sent turn ended. It does this with one wait with timeout_ms: 0, only for turns it doesn't know yet, in batches of 64. It caches the answers and drops them once the fire is gone.
  • What counts as failed:
  • Footer. It shows ✗ N failed in red and not dimmed, left of ↻. A click opens /background.
  • Margin glance. It shows a red ✗ N beside ↻, as a button to /background.
  • /background. Failed rows are listed first under "Failed". Each has a red ✗ mark, with the error in the default foreground under it. A server row can't be fired or removed, because the cleanup removes the fact once the server reads again.
  • The person's own agents. Failures of agents with no project in the name, such as memory-cleanup, show in every window.
  • The triggers tool. Its rows now carry last.turn_status and last.turn_error, so an agent sees what the person sees.
  • Footer spacing. The footer keeps at least one space before its right side, so a narrow window no longer runs main into the next item.

There is no daemon or protocol change. The design follows the visual-system thread (#405): the ✗ mark, its wording and placement, and failed rows first.

Screenshots

These come from the real daemon, using a turn that actually failed against an offline provider and a saved unreadable-chat fact.

/background

footer

Tests

  • a_fire_that_went_out_fails_when_its_turn_did covers a failed turn, an interrupted turn, a completed turn, and a turn not asked yet.
  • what_fails_for_the_person_shows_in_every_window_and_a_server_by_its_fact covers the scope rules and the server facts from both the user and project scopes.
  • the_card_lists_what_failed_first_marked_with_its_error
  • what_failed_in_the_background_is_red_left_of_what_runs, for the footer.
  • The margin glance test was extended.
  • cargo test -p sonny passes (507), and clippy and fmt are clean.

The README, docs/SONNY.md and the FEATURES row for /background are updated.


Generated by Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGEbQZnXUy1kATtYcQ6
…st in /background

A nightly run that failed while the laptop was offline or a login had
expired failed unseen: the trigger recorded "sent" and the turn it sent
then failed. Now the window asks the daemon how each trigger's last sent
turn ended (one `wait` with timeout 0, every ten seconds, for turns not
known yet), and counts as failed:

- a trigger whose fire failed, or whose sent turn failed (a turn the
  person interrupted or steered is not a failure);
- a server the nightly memory cleanup could not read, by its
  `unreadable-SERVER` fact (#411).

The footer says `✗ N failed` in red, left of `↻`; the margin glance has
a red `✗ N` beside `↻`; both open /background, which lists what failed
first with the error under it. The person's own agents' failures (no
project in the name, such as memory-cleanup) show in every window. The
triggers tool's rows say how the turn their last fire sent ended
(`last.turn_status`, `last.turn_error`), so an agent sees what the person
sees. No daemon or protocol change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGEbQZnXUy1kATtYcQ6
@lydakis lydakis self-assigned this Oct 30, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T03:46:02.606396Z 07d8937 New commits
🔒 Security Review ✅ Completed 2026-10-30T03:47:05.642486Z 07d8937 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 854d2c7b0c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sonny/src/served.rs Resolved
Comment thread sonny/src/background.rs Resolved
…ead only for this store's triggers

A one-shot that did not deliver, or a trigger stopped by an error, keeps
its row when its job goes; /background now lists such rows, scoped by the
store id each row now records, so the failure stays until `d` removes it.
The triggers tool asks this daemon only about triggers made on its store:
a turn handle names a bot and a turn, which another store's daemon has
its own of.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGEbQZnXUy1kATtYcQ6
…'s mark

`›` sat against the row's mark (`›✗`, `›↻`), which read as a glitch. It
now takes two columns, as the composer's `› ` does, and every line after
a row's first starts under its name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGEbQZnXUy1kATtYcQ6

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1d78a99b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sonny/src/main.rs
Comment on lines +6061 to +6064
let mut items = read(&self.turns);
let unended = background::unended(&items, &self.turns);
if !unended.is_empty() {
let ended = trigger::ended(&self.client, unended).await;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain sent one-shots until their turn outcome is known

When a one-shot trigger successfully submits a turn that subsequently fails, the new outcome lookup never sees its handle: trigger::settle immediately calls retire with keep = false for a successful send, deleting both the job and its state row. Consequently, neither read_around nor the triggers tool can discover that failure, even if Sonny remains open throughout. The same applies to the final successful submission of a --runs trigger. Preserve those handles until their turn outcomes can be checked so these failures also reach /background and the red footer count.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a real gap, but I'm not fixing it in this PR. The path: a one-shot, or a --runs trigger on its last run, sends its turn, settle deletes its row, and the turn fails afterwards. The recurring triggers this PR is for, such as the nightly cleanup, keep their rows and are covered. Fixing it means keeping sent rows past retirement, which changes the trigger lifecycle. Every finished one-shot would then stay in the agent's triggers list until something removes it. That needs a design call, so I filed it as #422.


Generated by Claude Code

Comment thread sonny/src/trigger.rs Resolved
Comment thread sonny/src/background.rs Resolved
…and a start trigger stays its adder's once it ends

A turn that went well but whose answer could not be passed on now counts
as failed, with the delivery error. A retired `--start` trigger's row
keeps the agent that added it (`by`), so its failure stays in that
project's windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGEbQZnXUy1kATtYcQ6
@lydakis
lydakis merged commit 696cd86 into main Oct 30, 2026
4 checks passed
@lydakis lydakis mentioned this pull request Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants