Synthesis: March 27, 2026
March 27, 2026
Three projects shipped today. The unifying theme was making failures visible — a day spent closing feedback loops that had been open for too long and building the infrastructure to keep them closed.
Paulos
The paulos session was short but covered a diagnostic win, a safety fix, and a meaningful pipeline upgrade.
The humanizer failure from the previous day was traced to a compound problem. Two API keys had expired (Anthropic and OpenAI), and load_dotenv(override=False) in cli.py meant that when the shell already had the old keys in its environment, updating .env had no effect — the shell’s stale values won. The fix was override=True, which makes .env always authoritative over whatever’s already in the environment. This is the kind of silent failure that’s hard to catch precisely because the system keeps running; humanize just passes through the original text when the LLM call fails, which looks like success until you notice the output didn’t change. The humanizer stop-list also got a small correction — “So the question worth sitting with” and “The question worth sitting with” were added as forbidden variants after the 2026-03-26 reflect script slipped one through.
The more significant work was adding --auto mode to the /next skill. The new mode implements a priority cascade designed to run inside Claude Code’s /loop command: first grind any ready-for-dev issues, then prep anything ready-for-prep, then triage untriaged issues (has milestone, no routing label), and finally run scout if all queues are empty. Each invocation does one unit of work and returns; /loop calls it again. This is a direct replacement for the launchd-based paulos orchestrate run daemon — the same behavior, but driven by Claude Code’s native loop mechanism rather than a separately managed process. The /close skill was updated to run CronList + CronDelete at session end, so loops stop cleanly instead of continuing after the session closes.
Issue #638 (RISKS.md audit) was also prepped — it’ll correct stale entries (three of five referenced issues have since closed) and file a tracking issue for the ten pre-existing test failures that have been masking regressions.
Completed: #637 (humanizer stop-list fix), #638 prepped and labeled ready-for-dev, Sentry DSN splits for eclectis-engine and authexis-engine, ECLECTIS-3 env var investigation and fix, /next –auto + /close cron cleanup.
Eclectis
Eclectis had a productive day across three distinct fronts: test coverage, Sentry triage, and UI polish.
Test coverage was the biggest single effort. Eleven issues closed in this area alone. The engine side added handler tests for briefing.generate (skip/send/render paths), newsletter.process (idempotency and content/link modes), and user.learn (persistence and learned search terms). The web side got billing webhook regression tests and article mutation tests covering vote toggles, archiving, and vote notes. Two post-merge fixes were also needed: the briefing.generate fixtures had mismatched URLs that caused article_by_url lookups to return None (item URLs in the fixture didn’t match the URLs the handler was querying by), and the push handler network-error tests were written expecting pytest.raises(httpx.ConnectError) when the handler had already been changed to catch the error and return {"status": "failed"} instead of re-raising. The Stripe webhook mock fix (#508) was particularly important — the original mock was missing getStripeWebhookSecret in the vi.mock call, which meant the test was silently passing without actually testing the real webhook verification path.
Sentry triage closed three stale issues — old preview deployment errors unrelated to the current codebase — and fixed two real bugs. The first was a foreign key constraint violation in deleteFeed (#524): a fire-and-forget trackEvent(feedId) call was racing the DELETE, sometimes sending a feedId that had already been removed. Fixed by omitting feedId on delete events. The second was a cluster of bare except blocks in Python handlers (#527) that were swallowing error details entirely, causing Sentry to show “(No error message)” for exceptions in briefing_generate.py, google_search_scan.py, website_scan.py, and the webhook JSON parse path in main.py. All four were patched with as e captures and appropriate log calls. Sentry user context (#526) was wired into the root layout via a SentryIdentify client component, so production errors now show affected user counts.
UI polish closed five issues from the previous milestone’s tail: dead source-management CTAs and broken feed row links (#492), admin usage aggregation moved server-side (#493, was loading raw ai_usage_logs into the browser), newsletter and podcast detail histories paginated (#494), mobile source navigation and scan feedback fixed (#495), and admin user fetching de-optimized (#496).
Completed: 22 issues closed. No carry-over — the queue is clear. Remaining backlog items are longer-horizon (signal amplification, AI search terms, browser extension, homepage social proof).
Authexis
Authexis shipped the day’s biggest feature: full end-to-end image upload infrastructure, landing in three product areas simultaneously.
Cloudinary image uploads replaced a previously-reverted Vercel Blob implementation that had been blocked because the Blob store was never provisioned. Cloudinary was already in use by the engine for DALL-E-generated images, so this consolidated the image story. The upload route (/api/upload/image) validates MIME type and size, streams to Cloudinary under authexis/user-uploads/{workspaceId}/, and returns a CDN URL. From there, three features landed in the same session: featured images on content detail (live uploader replacing the static <img>, optimistic UI update), brand reference images in workspace settings (a five-slot grid of visual references that feed into the AI image generation prompt), and image_url on social posts.
The social post image work was the most technically involved. It required a new DB column, an edit panel in the queue UI, thumbnail display on queue cards, and wiring publishing through all four platform integrations. Bluesky required the full AT Protocol path — upload image bytes to get a blob, embed the blob reference in the post record. Mastodon uses /api/v1/media for the upload, then attaches the media IDs to the status create call. LinkedIn uses the Community Management API’s asset registration flow (the r_liteprofile / w_member_social scopes and a two-step register-then-post pattern). Threads is simplest — it accepts a public URL directly via the IMAGE media type.
Sentry and reliability closed five more issues. The most impactful was a schema mismatch (#1750) where the c.style column was being queried in several handlers but had never been added to the database — this was generating 855 errors per day. Fixed with a migration and IF NOT EXISTS guard. A dead loop in notifications.py (#1748) was iterating slots.articles, a field removed in an earlier refactor. The MCP server (#1752) was propagating ClientDisconnect exceptions as 500 errors instead of logging cleanly and returning. The Claude API client (#1749) got tenacity-based retry logic (5 attempts, exponential backoff on RateLimitError) and an asyncio.Semaphore(5) to cap parallel calls. Sentry user context (#1753) was added to process_command(). An exc_info=True pass over eight engine files (#1751) replaced bare log swallows with proper exception capture.
Completed: 14 issues closed across image infrastructure and Sentry reliability. Queue is empty — no ready-for-prep or ready-for-dev items. v1.5 is 49/50 closed (one backlog item remaining).
Cross-cutting themes
Making failures visible. All three projects addressed the same underlying pattern: systems that fail silently because someone made a deliberate choice to not propagate errors. Paulos’s humanizer returning the original text when the LLM call fails. Eclectis’s and Authexis’s Python handlers using bare except blocks that swallowed exception details. Authexis’s ClientDisconnect being converted to 500s. In each case, the fix wasn’t new error handling — it was restoring the signal that had been deliberately suppressed. The discipline of letting things fail loudly, even at the cost of more Sentry noise, is what makes the feedback loop work.
Observability infrastructure matured. Sentry now has proper coverage across the full stack for both Eclectis and Authexis: Next.js web layer with user context, Python engine with user context and exception details, separate projects per layer so errors are attributable. The remaining gap is verifying that events are actually flowing in production — something to check next session.
Test infrastructure filling in. Eclectis’s test coverage blitz (11 issues) represents a significant shift in confidence. The mock fix (#508) was the most interesting — not a new test, but the discovery that an existing test had been passing without actually exercising the thing it claimed to test. That’s a particularly hard failure mode to notice, and it only became visible when someone read the mock call carefully enough to notice the missing import.
Pipeline automation. Paulos’s /next --auto is a conceptual shift: instead of maintaining a separate process (launchd agent, paulos orchestrate run) to cycle through the issue queue, Claude Code’s own /loop mechanism does the same work. The pipeline logic is the same; the execution substrate changed. This is worth watching — it’s the first piece of the orchestration layer that’s been internalized into Claude Code rather than wrapped around it.
Carry-over
| Item | Project | Status |
|---|---|---|
| Eclectis engine Sentry on main | eclectis | Committed to feature branch test/498-499-billing-and-article-mutations; needs merge |
| Reddit credentials | paulos | Paul needs to register a Reddit “script” app at reddit.com/prefs/apps |
| #638 (RISKS.md audit) | paulos | Labeled ready-for-dev; corrects stale entries, files test failure tracking issue |
| PRs #512, #513, #515, #516 | eclectis | Open from 2026-03-26; need review/merge |
| Verify Sentry/PostHog production events | eclectis | Unverified; worth a manual check next session |
Risks
| Risk | Project | Severity |
|---|---|---|
| Eclectis engine errors still going to wrong Sentry project until branch merges | eclectis | Medium |
LinkedIn image publishing may silently fall back to text-only if token predates w_member_social scope | authexis | Low |
| Supabase CLI migration sync fragility — local/remote drift possible | authexis | Medium |
By the numbers
| Project | Issues closed | Notes |
|---|---|---|
| Paulos | 1 | + 1 prepped (#638) |
| Eclectis | 22 | 11 test coverage, 5 Sentry, 5 UI polish, 1 schema fix |
| Authexis | 14 | Image infrastructure (4), Sentry/reliability (5), social post image (3), closed stale (2) |
| Total | 37 |
Milestone status: Paulos has no open milestones. Eclectis has no open milestones. Authexis v1.5 is 49/50 (one backlog item, effectively complete).
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.
Silence by design
Most systems have more suppression than their owners realize. It gets installed for good reasons. The cost accumulates slowly, in the form of systems you can't operate because you've removed the signals that would let you understand them.
Designed to learn, built to ignore
The most dangerous organizational failures don't throw errors. They look fine, return results, and quietly stay frozen at the moment of their creation.
The variable that was never wired in
The gap between having a solution and using a solution is one of the most persistent failure modes in organizations. You see the escaped variable. You see the risk register. You assume the work is done.