Willison bars AI ownership, sqlite-utils blocks cascade, shot-scraper runs JS
Simon Willison publishes an anti-agent-ownership essay the same day his sqlite-utils fixes a cascade bug and his shot-scraper adds unpinned remote fetch.
Willison bars AI ownership, sqlite-utils blocks cascade, shot-scraper runs JS
TL;DR
- Willison argues no LLM agent can hold Directly Responsible Individual status on a project.
- California AB 316 legislates the same rule effective Jan 1 2026, blocking autonomy defenses.
- sqlite-utils 4.1.1 raises TransactionError instead of silently cascading deletes during transform.
- shot-scraper 1.11 adds a
gh:remote-JS shortcut with no lockfile or integrity hash. - 70%+ of enterprises spot multi-agent failures without identifying which sub-agent caused them.
Today’s AI-tech shipping list is a Simon Willison monoculture, and that’s the frame. Willison argues in a long essay that no LLM agent should ever hold Directly Responsible Individual status, because accountability is a uniquely human act — one that California’s AB 316 will fold into tort law by January, forbidding defendants from blaming AI autonomy for harm.
In the same 24 hours, two of his own tools ship point releases that test where that line actually sits in code. sqlite-utils 4.1.1 hardens a silent ON DELETE CASCADE data-loss path that Claude surfaced during an exploratory chat — the guard is Willison’s, the discovery was the model’s. shot-scraper 1.11 goes the other way, adding a gh:username/script shortcut that fetches and executes remote JavaScript with no lockfile and no integrity hash. The principle is that humans own outcomes; the practice is that a solo maintainer keeps deciding, one PR at a time, which agent-adjacent risks to close and which to leave open.
Willison: no AI agent should ever be a project’s DRI
Source: simon-willison · published 2026-07-12
TL;DR
- Simon Willison argues LLM agents should never hold Directly Responsible Individual status — accountability is a uniquely human act.
- His $149.25 sqlite-utils 4.0 build with Claude Fable produced 1,321 lines across 30 files in 37 prompts.
- California’s AB 316, effective Jan 1 2026, already legislates the same rule — defendants cannot blame AI autonomy for harm.
- 70%+ of enterprises can detect a multi-agent failure but can’t identify which sub-agent caused it.
The claim, and the receipt
Willison’s post is short and unambiguous: a Directly Responsible Individual — Apple’s term for the single person “ultimately accountable for the success or failure” of a project, as codified in GitLab’s handbook — must be human. He reaches for IBM’s 1979 slide (“a computer can never be held accountable, therefore a computer must never make a management decision”) to close the argument.
What gives the post weight is that he’s arguing it from the strong side. His companion sqlite-utils 4.0 release, built almost entirely by Claude Fable for $149.25 in API fees, produced 1,321 lines across 30 files in 37 prompts — and, per the recap, the agent flagged a silent data-loss bug in delete_where() that Willison concedes he would have missed 1. The human isn’t the DRI because the machine is incompetent. The human is the DRI despite the machine sometimes being the better reviewer.
The moral crumple zone
Willison’s line lands cleanly as ethics. It collides with a decade of sociotechnical research that calls the resulting human role a “moral crumple zone” — Madeleine Elish’s 2016 term for an operator engineered into the system specifically to absorb blame for failures they couldn’t meaningfully prevent 2.
“The human may become a component that absorbs the moral impact of a disaster to protect the reputation and integrity of the technological framework.” — Elish, Moral Crumple Zones 2
The 2026 legal environment is hardening exactly this shape. California’s AB 316, effective January 1, explicitly bars defendants from arguing that an AI system caused harm autonomously; a human entity must remain on the hook for an agent’s conduct 3. Willison is describing an ethical stance. The statute is describing a liability allocation mechanism. They agree on the answer and disagree on the reason, which is a distinction worth keeping.
The attribution gap
The uglier problem is operational. A DRI who cannot trace the failure is a DRI in name only, and multi-agent architectures are making tracing hard: over 70% of enterprise leaders report they can detect a system error but cannot identify which sub-agent was responsible 4. One revisit of the 1979 IBM memo puts the political question bluntly — does the human-accountability principle “protect employees from machines, or protect executives from their own technology’s failures?” 5.
The macro numbers frame the stakes. Forrester puts roughly 88% of agentic AI pilots as never reaching production, with Gartner projecting ~40% of current projects cancelled by 2027 on risk-control and ROI grounds 6. The DRI role is being written into contracts, statutes, and org charts faster than the observability stack that would let a DRI actually do the job.
What’s missing
Willison’s essay is the clean ethical case: agents execute, humans answer. The bundle around it suggests the harder problem is mechanism. A human DRI at agent speed, over a multi-agent stack with 70% attribution failure, is closer to a safety-driver seat than an accountable owner. The question isn’t whether the DRI should be human — Apple, GitLab, California, and Willison already agree. It’s what tooling makes that accountability more than a signature on a liability form.
sqlite-utils 4.1.1 blocks a silent CASCADE data-loss bug
Source: simon-willison · published 2026-07-12
TL;DR
- sqlite-utils 4.1.1 makes
table.transform()raiseTransactionErrorinstead of silently firingON DELETE CASCADEon referenced rows. PRAGMA foreign_keyscan’t be toggled inside a transaction, so the library can’t defensively disable FKs before the transform’s drop step.- Alembic and Atlas perform the same drop-and-swap dance without this guard, leaving sqlite-utils the stricter option.
- Claude surfaced the bug during an exploratory
ON DELETEchat, extending Willison’s “model stress-tests new code” workflow.
The footgun 4.1.1 closes
table.transform() is sqlite-utils’ packaging of SQLite’s canonical 12-step recreate procedure — the workaround Stack Overflow has recommended for over a decade because ALTER TABLE cannot change column types, drop columns, or add foreign keys 7. The dance is: create a new table with the desired schema, copy rows across, drop the old table, rename the new one into place.
The “drop the old table” step is where things go quietly wrong. If any other table references the one being transformed via ON DELETE CASCADE, SET NULL, or SET DEFAULT, dropping it fires those actions against the referencing rows — a routine schema change becomes a silent data-loss event 8. The safe workaround is to disable PRAGMA foreign_keys before the transform, but that pragma is connection-scoped and cannot be changed inside an open transaction. SQLx hit the same wall and had to route users toward PRAGMA defer_foreign_keys instead 9.
sqlite-utils could have followed suit, but defer_foreign_keys only postpones constraint failures — it does not stop cascade actions from firing when the parent table disappears 8. So 4.1.1 takes the conservative route: detect the dangerous configuration, refuse to run, and force the caller to structure the transform outside a transaction or handle FKs manually.
Stricter than the incumbents
flowchart LR
A[table.transform called] --> B{Transaction open?}
B -- No --> R[Run 12-step recreate]
B -- Yes --> C{PRAGMA foreign_keys ON?}
C -- No --> R
C -- Yes --> D{Table referenced by<br/>destructive ON DELETE?}
D -- No --> R
D -- Yes --> X[Raise TransactionError]
Alembic’s SQLite “batch mode” performs the identical recreate-and-swap and makes no attempt to guard against destructive ON DELETE actions; Atlas’s declarative diff has the same blind spot, assuming callers have already disabled FKs 10. For a tool that is usually filed under “utility” rather than “migration framework,” sqlite-utils is now the one drawing the tighter safety line — refusing to run rather than trusting the caller.
There is also a subtler interaction lurking in modern SQLite: ALTER TABLE ... RENAME rewrites FK references in other schema objects unless legacy_alter_table=ON, which can collide with the swap step in ways that older recreate scripts never anticipated 11. The TransactionError guard sidesteps that whole class of surprise.
Claude as edge-case fuzzer
The provenance matters as much as the patch. Willison has documented a pattern of feeding whole modules to Claude and asking it to “manually exercise” new features hunting for edge cases 12. Issue #794 is a clean data point: reproducing this bug required simultaneous knowledge of pragma scoping, transaction semantics, and the transform’s internal drop order — exactly the cross-cutting logic manual tests miss. The library got stricter, and the review loop that made it stricter is starting to look less like an anecdote and more like a repeatable methodology.
shot-scraper 1.11 swaps 1s server wait for 30s polling
Source: simon-willison · published 2026-07-12
TL;DR
- shot-scraper 1.11 replaces the fixed 1-second server startup delay with 30-second port polling.
- New
--js-fileflag loads JavaScript from a local file, stdin, orgh:username/script. - The
gh:shortcut fetches remote JS with no lockfile or integrity hash, executing whatever the repo currently contains. - Practitioner verdict unchanged: great for demos, wrong tool at 90k sites on a 22-core box.
What 1.11 actually fixes
Every headline change in this release targets the exact workflow shot-scraper’s heaviest users already run in production — batch multi jobs on CI runners that spin up a local Datasette or Node server, screenshot it, and tear it down. The old server: mechanism gave that server exactly one second to start accepting traffic before firing Playwright at it, which is fine on a warm laptop and unreliable on a cold GitHub Actions VM. 1.11 replaces the fixed delay with active port polling, extended to a 30-second ceiling. That’s the fix; the rest of the changelog is CLI hygiene.
The --js-file addition matters more than it looks. Previously, injecting non-trivial JavaScript into shot-scraper, pdf, html, accessibility, or har meant either a shell heredoc or a very long --javascript string argument — awkward in YAML, worse in shell scripts committed to a repo. Now you point at a file (or stdin, or a GitHub path) and multi picks up the same idiom via js_file:. It’s the small ergonomic that stops long-running batch configs from becoming unreadable.
The gh:username/script trust boundary
The gh:username/script shortcut deserves more attention than the release notes give it. Scripts are fetched live from GitHub with no pinning, no lockfile, no hash 13. A typo-squatted username or a compromised account executes arbitrary JavaScript inside your browser session — and since shot-scraper is often invoked with --auth cookies.json or --bypass-csp for scraping logged-in pages, that JS inherits whatever authenticated context you handed the browser. This isn’t RCE on the host, but “in-browser code with your session cookies” is a meaningful capability to grant a URL you didn’t version-pin.
If you use gh:, treat it the way you’d treat curl | bash: pin to a fork you control, or vendor the script into your repo and use --js-file ./script.js instead.
Where it stops being the right tool
Practitioner dissent has been consistent for years and 1.11 doesn’t change it. One Hacker News user described pointing shot-scraper at 90,000 sites and overheating a 22-core machine, arguing Scrapy would have used an order of magnitude less power 14. Independent comparisons cite roughly 150MB of RAM per Chromium instance and the total absence of stealth or proxy rotation as reasons to reach for a managed screenshot API — ScreenshotOne, Urlbox — once you’re past hobby volume 15. A community fork, shot-power-scraper, swaps Playwright for nodriver specifically to add CAPTCHA and fingerprint evasion that upstream has declined to pursue 16.
That scoping is deliberate, and 1.11 confirms it. The audience being served is Reuters generating recurring dashboard graphics for newsletters, Ben Welsh’s @newshomepages archiving news front pages daily, and the Datasette docs rebuilding UI screenshots on release 17 — batch jobs where the one-second server race was the actual bug. If you’re scraping at scale or against hostile WAFs, this release isn’t for you and never was.
Footnotes
-
clauding.de recap of Willison’s sqlite-utils 4.0 build — https://clauding.de/en/posts/willison-fable-sqlite-utils-149-dollar/
↩The total unsubsidized cost for the development of the 4.0rc2 release was calculated at $149.25 in Claude API fees… the agent responded to 37 prompts and generated 34 distinct commits, totaling 1,321 new lines of code across 30 files.
-
Madeleine Clare Elish, ‘Moral Crumple Zones’ (We Robot 2016) — https://robots.law.miami.edu/2016/wp-content/uploads/2015/07/ELISH_WEROBOT_cautionary-tales_03212016.pdf
↩ ↩2A human actor is held legally or morally responsible for the failure of an automated system, despite having limited control over its operations… the human may become a component that absorbs the moral impact of a disaster to protect the reputation and integrity of the technological framework.
-
Jones Walker AI Law Blog on 2026 liability regime — https://www.joneswalker.com/en/insights/blogs/ai-law-blog/nists-ai-agent-standards-initiative-why-autonomous-ai-just-became-washingtons.html?id=102mkh6
↩A California statute effective January 1, 2026, explicitly bars defendants from claiming that an AI system caused harm autonomously, effectively mandating that a human entity remains responsible for an agent’s conduct.
-
Digital Journal on multi-agent attribution failures — https://www.digitaljournal.com/article/autonomous-ai-creates-a-new-enterprise-risk-when-systems-fail-no-one-knows-why/
↩Over 70% of leaders report they can detect a system error but struggle to identify which specific sub-agent was responsible for the failure.
-
Indrajit, ‘The 1979 IBM AI Memo Revisited’ — https://indrajit.co.in/2026/03/17/the-1979-ibm-ai-memo-revisited-is-human-accountability-still-superior/
↩Humans often use ‘the computer made a mistake’ as a shield to avoid personal consequences, making the 1979 warning idealistic rather than practical… does the 1979 principle serve to protect employees from machines, or to protect executives from their own technology’s failures?
-
Forrester, ‘State of Agentic AI in 2026’ — https://www.forrester.com/blogs/the-state-of-agentic-ai-in-2026-companies-are-chasing-few-are-catching/
↩Roughly 88% of agentic AI pilots never reach production… Gartner forecasts that nearly 40% of current agentic projects will be canceled by 2027 due to inadequate risk controls and unclear ROI.
-
Stack Overflow: adding a foreign key to an existing SQLite table — https://stackoverflow.com/questions/1884818/how-do-i-add-a-foreign-key-to-an-existing-sqlite-table
↩SQLite does not support adding a foreign key with ALTER TABLE; the accepted workaround is the 12-step recreate procedure — disable FKs, create new table, copy rows, drop old, rename, re-enable FKs, run PRAGMA foreign_key_check.
-
coddy.tech SQLite foreign keys guide — https://coddy.tech/docs/sqlite/foreign-keys
↩ ↩2ON DELETE CASCADE / SET NULL / SET DEFAULT propagate automatically when the referenced row (or table) disappears — a behaviour that turns a routine DROP TABLE inside a transform into a silent data-loss event.
-
GitHub issue: launchbadge/sqlx #2085 — https://github.com/launchbadge/sqlx/issues/2085
↩PRAGMA foreign_keys cannot be changed inside a transaction; migration tools that auto-wrap in BEGIN…COMMIT must instead use PRAGMA defer_foreign_keys = ON, which is permitted mid-transaction.
-
r/Python thread on Alembic vs Atlas — https://www.reddit.com/r/Python/comments/191kuqx/sqlalchemy_migrations_goodbye_alembic_hello_atlas/
↩Commenters note Alembic’s ‘batch mode’ automates the 12-step dance for SQLite but does not guard against destructive ON DELETE side-effects; Atlas’s declarative diff similarly assumes the user has disabled FKs before applying.
-
sqlite.org changelog — https://sqlite.org/changes.html
↩Legacy behaviour where ALTER TABLE … RENAME did not rewrite references in other schema objects is preserved only under PRAGMA legacy_alter_table=ON; modern SQLite rewrites FK references during rename, which can conflict with the drop-and-swap pattern.
-
Simon Willison — code-review tag archive — https://simonwillison.net/tags/code-review/
↩Willison’s documented pattern is to feed entire modules to Claude and ask it to ‘manually exercise’ new features hunting for edge cases — the same adversarial loop that surfaced issue #794.
-
The AI Nuggets — ‘Automate Browser Screenshots and Demos with shot-scraper’ — https://theainuggets.com/automate-browser-screenshots-and-demos-with-shot-scraper/
↩gh:username/script syntax lets you execute JavaScript directly from a public GitHub repository… the user is implicitly trusting the GitHub account holder
-
Hacker News discussion on shot-scraper — https://news.ycombinator.com/item?id=39442740
↩scraping 90,000 sites with the tool… 22-core system overheated; a dedicated framework like Scrapy would likely have used an order of magnitude less power
-
dev.to — ‘Screenshot API vs Headless Browser’ — https://dev.to/pollop/screenshot-api-vs-headless-browser-which-is-best-for-your-use-case-j04
↩high RAM consumption—often 150MB per instance—and the need for complex proxy rotation to bypass anti-bot protections like Cloudflare
-
Hacker News commenter (shot-power-scraper fork) — https://news.ycombinator.com/item?id=39442740
↩community fork shot-power-scraper replaces Playwright with nodriver to provide enhanced stealth and CAPTCHA evasion
-
The AI Nuggets — production patterns — https://theainuggets.com/automate-browser-screenshots-and-demos-with-shot-scraper/
↩Reuters uses the tool to generate regularly updating data dashboards for email newsletters; Ben Welsh’s @newshomepages bot screenshots news sites daily