Skip to main content
Paul Welty, PhD AI, WORK, AND STAYING HUMAN

Work log — 2026-04-02

What shipped today

Five issues closed — a mix of security hardening, UX polish, test coverage, and dead-code discovery. The ready-for-dev queue went from four items to zero.

The most impactful fix was #655: the podcast RSS feed endpoint was using a LIKE query with a loose regex, allowing any prefix of a valid hash to authenticate as that user’s feed. Tightened to an exact 12-char hex match with .eq(), matching the pattern already used by the sibling RSS feed endpoint. Two lines, high leverage.

The user-facing improvement was #658: new users completing onboarding were dropped onto a blank articles page with no context about when content would arrive. Added a welcome banner that shows their configured briefing time and timezone, explaining that sources are being scanned and when to expect the first briefing. The banner auto-hides once a briefing arrives or after 48 hours — no dismiss button needed, the server action returns null when conditions change.

#657 was half-stale — the issue claimed zero test coverage for ai.py and claude.py, but test_ai_service.py (13 tests) and test_claude_json.py (14 tests) already existed. The actual gap was claude.chat() retry logic: 429/500 retry with backoff, connection/timeout retry, max retry exhaustion, non-retryable immediate raise, and BYOK fresh client creation. Added 8 tests in test_claude_chat.py.

#656 turned out to be already resolved — Raindrop/Readwise push handlers are triggered from article_score.py at scoring time, not from the briefing flow. The idempotency key deduplicates, so adding the same logic to briefing_generate.py would have been dead code. Closed with explanation.

Three new prospect-driven issues (#659, #660, #661) came in from a client conversation but were too vague to spec. Routed #661 and #660 to needs-clarification with specific questions, and #659 to blocked (depends on #661). Also added a DECISIONS.md entry clarifying the boundary: product features go in GitHub, client engagement work goes in Notion.

Completed

  • #654 — SSRF: block unsafe URLs in website_scan and article_fetch (landed before session, from yesterday’s late push)
  • #655 — Podcast RSS hash endpoint: exact match instead of LIKE prefix collision
  • #656 — Raindrop/Readwise push: closed as already resolved (wired in article_score.py)
  • #657 — AI service tests: added 8 retry/BYOK tests for claude.chat()
  • #658 — Welcome banner for new users on articles page

Carry-over

No milestones are open. The remaining open issues break down as:

  • needs-clarification (2): #661 (curated briefings), #660 (onboarding content upload) — both waiting on product direction from Paul
  • blocked (1): #659 (white-label briefings) — depends on #661
  • backlog (7): Sentry errors (#593, #546), future product ideas (#484, #482, #481, #480, #471, #64)
  • engine/uv.lock has uncommitted drift from a dependency install — not urgent but should be committed or reset

Risks

None new. The SSRF fix (#654) and hash collision fix (#655) both reduce the attack surface. No fragile dependencies introduced.

Flags and watch-outs

  • The three prospect issues (#659-661) need Paul’s product direction before they can move forward — they’ll sit in clarification/blocked until then
  • Sentry issues #593 and #546 are still in backlog representing real production errors (66 events on the SyntaxError one)
  • engine/uv.lock drift persists from last session — should be reviewed

Next session

  1. Investigate Sentry #593 (ESM/CJS require error on briefings detail page) — check if it’s still occurring and what triggers it
  2. Investigate Sentry #546 (SyntaxError on root page, 66 events) — significant user-facing noise worth diagnosing
  3. Run /scout — all queues are empty and milestones complete, good time for a deep sweep to surface new work
  4. Commit or reset engine/uv.lock — the lock file drift has carried over two sessions now

Why customer tools are organized wrong

This article reveals a fundamental flaw in how customer support tools are designed—organizing by interaction type instead of by customer—and explains why this fragmentation wastes time and obscures the full picture you need to help users effectively.

Infrastructure shapes thought

The tools you build determine what kinds of thinking become possible. On infrastructure, friction, and building deliberately for thought rather than just throughput.

Server-side dashboard architecture: Why moving data fetching off the browser changes everything

How choosing server-side rendering solved security, CORS, and credential management problems I didn't know I had.

The work of being available now

A book on AI, judgment, and staying human at work.

The practice of work in progress

Practical essays on how work actually gets done.

Delegation without comprehension is just prayer

The organizations that survive won't be the ones that automated the most. They'll be the ones that figured out what to stop delegating.

The case for corporate amnesia

Most organizations worship institutional memory. But what if the thing they're preserving is mostly decay?

Your design philosophy is already written

Builders who work across multiple projects leave fingerprints everywhere. The same mind solves the same problem differently in every domain — and usually doesn't notice. You need someone to read it back to you.