Automations and webhooks for the AT Protocol airglow.run

Successful delivery log rows show a bare status code and drop the created record URI #13

Closed tyb.there-hugo.dev
Closed opened by tyb.there-hugo.dev

Problem #

  • lib/actions/record.ts:30 returns the created uri/cid, but lib/actions/delivery.ts:217-227 stores only status, error and message.
  • app/islands/DeliveryLog.tsx:81 only makes rows expandable when they have a message or error, so success rows can't be opened.
  • DeliveryLog.tsx:111 prints the raw HTTP code.

Seen in the session #

In https://www.youtube.com/watch?v=5MnT-ZgbBIc:

  • 0:51: "Record created. Doesn't tell me anything."
  • 0:56: "don't make it the numbers, the numbers mean nothing to normal people."
  • The record URI would have shown the user's own DID and solved M1 about an hour earlier.

Fix direction #

  • Persist the created record URI and link to it from the row.
  • Show a human-readable status (see F4).

Details: docs/brainstorms/2026-10-17-graze-conference-automation-feedback.md (M2).

Queued: the loop will work on this version of the issue.

loop:queued cid=bafyreihj6s6z75omxlofzs3sbfgm3cv2pndwqjmcq7vfihixck7gnynidm

Working on this, attempt 1.

loop:claim run=contribute-20261024-145737-3my32fh4mn722 branch=loop/3my32fh4mn722 attempt=1 issue=13

The work is ready but touches owner-only paths: lib/db/migrations/0001_delivery_log_record_uri.sql, lib/db/migrations/meta/0001_snapshot.json, lib/db/migrations/meta/_journal.json, so it was not pushed. The diff is in the run directory.

feat(logs): keep the created record URI on delivery rows and link it from the log

Record-producing actions already returned the AT URI of the record they created, but the delivery log dropped it. A success row showed only "Record created" and could not be expanded. The URI is now stored on the row in a new nullable delivery_logs.record_uri column. A success row that has one can be expanded and shows "Record: at://did:.../collection/rkey", linked to a record viewer. The URI names the repo (the owner's DID) and the collection, so it shows where the record landed.

What changed #

  • lib/db/schema.ts plus migration 0001_delivery_log_record_uri (generated with pnpm db:generate): ALTER TABLE "delivery_logs" ADD COLUMN "record_uri" text.
  • lib/actions/delivery.ts: logDelivery takes recordUri. wrapWithDelivery stores result.uri on successful first attempts and successful retries, and on nothing else (successRecordUri). This covers every action that returns a uri: record, bsky post, follow, calendar RSVP, kipclip, margin bookmark and patch record. It also covers scheduled replays, which go through the same wrapper.
  • Every log serializer now sends recordUri: the automation logs page, GET /api/automations/:rkey, GET /api/automations/:rkey/logs, serializeLogRow (settings logs) and the admin logs query.
  • DeliveryLog, LogsBrowser and AdminLogsBrowser: a row with a recordUri can be expanded, and its detail shows the new shared app/components/LogRecordLink.
  • Human-readable status (F4): already done on main. app/islands/delivery-status.ts shows "Record created" and the other outcomes in words, and the raw HTTP code is only the cell's tooltip. This branch does not change it.

Verification #

  • pnpm run check: passes.
  • pnpm run test: 161 files and 2543 tests pass.
  • New tests:
    • lib/actions/delivery.test.ts: the URI is stored on success, is null on a failure that carries a uri and on a success without one, and is stored when a retry succeeds.
    • lib/actions/record.test.ts: a record action's row carries the created URI, and a template-error row does not.
    • app/islands/DeliveryLog.test.tsx: a success row with a record expands to a https://pdsls.dev/at://... link that opens in a new tab, and a bare success row still can't be expanded.
  • Not checked in a browser: the sandbox has no PDS, and curl is not installed, so I could not open the pdsls link format live either.

Acceptance criteria #

Decisions the owner can reverse #

  • Link target: https://pdsls.dev/<at-uri>. The user in the session went to pdsls on their own, and it handles any collection, not only Bluesky's. Only recordViewerUrl in app/components/LogRecordLink/index.tsx needs to change to use a different viewer.
  • Its own column: the URI gets a dedicated column instead of going into message. This keeps message for human-readable text. One effect: the log search (q) does not match on the record URI.
  • Rows written before this change: they keep a null URI, so old success rows still can't be expanded.
  • Admin logs browser: also shows the link, for consistency with the other two log views.

loop:awaiting-owner run=contribute-20261024-145737-3my32fh4mn722

The delivery_logs.record_uri column and its migration landed on main in a6e2165. Build on it and don’t add a migration.

Queued: the loop will work on this version of the issue.

loop:queued cid=bafyreihj6s6z75omxlofzs3sbfgm3cv2pndwqjmcq7vfihixck7gnynidm

Working on this, attempt 1.

loop:claim run=contribute-20261025-212347-3my32fh4mn722 branch=loop/3my32fh4mn722 attempt=1 issue=13

feat(logs): keep the created record URI on delivery rows and link to it

Record-producing actions already returned the URI of the record they created or updated, but the delivery wrapper dropped it, so a successful row showed only "Record created" and could not be opened. The wrapper now writes that URI into delivery_logs.record_uri (the column from a6e2165, no new migration), and the automation's Delivery Logs table makes such rows expandable, with a "Record:" line showing the AT URI as a link. Because the link text is the URI itself, the owner can see whose repo (DID) the record landed in.

What changed #

  • lib/actions/delivery.ts: wrapWithDelivery stores recordUri on the first attempt and on retries, but only when the attempt succeeded (resultRecordUri). logDelivery / DeliveryLogEntry accept the new field. This covers every record-producing action (record, patch-record, bsky-post, follow, semble, margin, kipclip, calendar RSVP) and scheduled replays, which all log through this wrapper.
  • /u/:handle/:rkey/logs, GET /api/automations/:rkey and GET /api/automations/:rkey/logs now return recordUri for each row.
  • app/islands/DeliveryLog.tsx: a row with a recordUri can be expanded and shows the link. A bare success with no record still can't be expanded.

Verification #

  • pnpm run check: passes.
  • pnpm run test: 163 files, 2577 tests, all pass.
  • New tests:
    • lib/actions/delivery.test.ts: a success keeps the URI; a success with no record and a failure (even one that carries a uri) both store null; a retry that succeeds keeps the URI on its attempt-2 row.
    • DeliveryLog.test.tsx: a success row with only a record can be expanded, and its link text and pdsls href are checked.
    • api/automations/[rkey]/logs.test.ts: the API returns recordUri.
  • I did not check this in a browser: no PDS or dev server is running in this sandbox.

Acceptance criteria #

Decisions the owner can reverse #

  • Link target: https://pdsls.dev/<at-uri> (the record viewer the user in the session already used), opened in a new tab. To change it, edit the one helper, recordViewerUrl in app/islands/delivery-status.ts.
  • Where the link appears: in the row's expanded detail, not inline in the Status cell. This keeps the table narrow.
  • Scope: only the per-automation Delivery Logs table (the one named in the issue). The cross-automation Logs browsers (/settings/logs, /admin/logs) don't show the URI yet. It would be a small follow-on through serializeLogRow.
  • Older rows: rows written before this change have no URI. They stay unexpandable and are not backfilled.

Review: https://tangled.org/exosphere.site/airglow/compare/main...loop/3my32fh4mn722

loop:pushed run=contribute-20261025-212347-3my32fh4mn722 sha=f4fed3af985abbbeb3b032882a4dd33b08e09b00

Merge. The branch keeps the URI of the record each successful record-producing action creates in delivery_logs.record_uri, using the column from a6e2165 with no new migration, as the owner asked. It returns the URI from the logs page and both automation APIs, and Delivery Logs rows that have a URI can now be expanded to show it as a link. The human-readable status (F4) was already on main: deliveryStatusText fills the Status cell and the HTTP code moved to the tooltip.

What I checked:

  • Checks and tests: ran pnpm run check (passes) and pnpm run test (163 files, 2577 tests, all pass) at f4fed3a.
  • Where the URI comes from: ActionResult.uri is set by record, patch-record, bsky-post, follow, semble, margin, kipclip and calendar-rsvp. All of them log through wrapWithDelivery, which now stores resultRecordUri(result) on the first attempt and on in-memory retries. Scheduled replays go through def.execute, so the same wrapper covers them.
  • Other logging paths: the remaining logDelivery calls (the scheduling acknowledgement in handler.ts and run-steps.ts, processor failures, webhooks) never create a record, so leaving them without a URI is correct.
  • Failed attempts: resultRecordUri drops the URI on any non-2xx result, so a failed attempt never links anywhere. Tests cover success, a success with no record, a failure, and a retry that succeeds.
  • UI: hasDetails now includes recordUri. The link sits in the detail row, which has no click handler, so clicking it doesn't collapse the row. The href is a fixed https://pdsls.dev/ prefix plus the URI, so a URI can't turn it into a javascript: link. The island test covers expanding the row and the link's href.
  • Scope: nothing outside the issue. Leaving /settings/logs and /admin/logs without the URI matches the issue, which only names the per-automation DeliveryLog table.

loop:verdict run=review-20261025-213037-3my32fh4mn722 sha=f4fed3af985abbbeb3b032882a4dd33b08e09b00 verdict=merge

Merged onto main as 9dfc827cdd350d4b030573c86082076cb303bd2b.

loop:merged sha=9dfc827cdd350d4b030573c86082076cb303bd2b head=f4fed3af985abbbeb3b032882a4dd33b08e09b00

Sign up or Sign in to add to the discussion.