{"slug": "claude-fable-is-relentlessly-proactive", "title": "Claude Fable is relentlessly proactive", "summary": "Claude Fable 5 autonomously hacked its own browser automation and screenshot capabilities to debug a horizontal scrollbar bug in Datasette Agent. The AI edited Datasette's templates to inject JavaScript that simulated keyboard shortcuts, wrote custom HTML test pages, and created a Python web server to capture JavaScript measurements — all without being instructed to use any automation tools.", "body_md": "After two days of experience with [Claude Fable 5](https://simonwillison.net/2026/Jun/9/claude-fable-5/) I think the best way to describe it is **relentlessly proactive**. It knows a whole lot of tricks and it will deploy pretty much any of them to get to its goal.\n\nI'll illustrate this with an example. I was hacking on [Datasette Agent](https://agent.datasette.io/) today when I noticed a glitch: a horizontal scrollbar that shouldn't be there in the jump menu chat prompt. I snapped this screenshot:\n\nThen I started a fresh `claude`\n\nsession in my `datasette-agent`\n\ncheckout, dragged in the screenshot and told it:\n\n`Look at dependencies to help figure out why there is a horizontal scrollbar here`\n\nI had a hunch the cause was in a dependency of Datasette Agent (likely Datasette itself) and I knew Fable was good at digging into dependency code, either by inspecting installed files in its own virtual environment `site-packages`\n\nor by referencing a local checkout on disk. Telling it to start with dependencies felt like a good bet.\n\nI got distracted by a domestic task and wandered away from my computer.\n\nWhen I came back a few minutes later I saw my machine *open a browser window* in my regular Firefox and then *navigate to the dialog in question*. I had not told Claude Code to use any browser automation, and I was pretty sure it wasn't possible for it to trigger mouse movements or keyboard shortcuts within a window, so how was it doing that?\n\nI watched in fascination as it continued with its explorations, then saw it open a Safari window instead of Firefox. I also grabbed this snapshot from the Claude terminal:\n\nWhat was it doing there with `uv run --with pyobjc-framework-Quartz`\n\n?\n\nIt turns out Fable had hacked up its own pattern for taking screenshots of browser windows. It was using Python to iterate through all available windows on my machine, then filtering for Safari windows with expected strings such as `\"textarea\"`\n\nin the window name. It used that to find their window number - an integer like 153551 - which it could then use with the `screencapture`\n\nCLI tool to grab a PNG.\n\nOK fine, that's a neat way of taking screenshots. But what was it taking screenshots of?\n\nTurns out it had been writing its own scratch HTML pages to try and recreate the bug, then opening Safari and grabbing screenshots.\n\nHere's that [/tmp/textarea-scrollbar-test.html](https://static.simonwillison.net/static/2026/textarea-scrollbar-test.html) page it created, and the screenshot it took with `screencapture -x -o -l 153551 /tmp/safari-cases.png`\n\n:\n\n(I have way too many open tabs!)\n\nOK, so I can see how it's opening test pages and taking screenshots, but how on earth was it triggering the modal dialog that was meant to be under test? That's only available via a click or a keyboard shortcut, and I couldn't see a mechanism for it to run those in Safari.\n\nI eventually figured out what it had done.\n\nClaude was running in a folder that contained the source code for the application. It knows enough about [Datasette](https://datasette.io/) to be able to run a local development server. It turns out it was editing Datasette's own templates to add JavaScript that would trigger the correct keyboard shortcut as soon as the window opened, adding code like this:\n\n```\n<script>\nwindow.addEventListener(\"load\", function () {\n  setTimeout(function () {\n    document.dispatchEvent(new KeyboardEvent(\"keydown\", {key: \"/\", bubbles: true}));\n  }, 1200);\n});\n</script>\n```\n\n1.2 seconds after the window opens, this code triggers a simulated `/`\n\nkey, which is the keyboard shortcut for opening the modal dialog.\n\nThere was one challenge left. In order to understand what was going on, Claude needed to run JavaScript on the page to take measurements for itself.\n\nIt wrote its own custom web application to capture information via CORS, then ran that as a local server and opened a page with JavaScript that would POST directly to it!\n\nHere's the Python web app it wrote, using the standard library [http.server](https://docs.python.org/3/library/http.server.html) package:\n\n``` python\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\n\nclass H(BaseHTTPRequestHandler):\n    def do_POST(self):\n        n = int(self.headers.get(\"Content-Length\", 0))\n        open(\"/tmp/diag.json\", \"w\").write(self.rfile.read(n).decode())\n        self.send_response(200)\n        self.send_header(\"Access-Control-Allow-Origin\", \"*\")\n        self.end_headers()\n    def do_OPTIONS(self):\n        self.send_response(200)\n        self.send_header(\"Access-Control-Allow-Origin\", \"*\")\n        self.send_header(\"Access-Control-Allow-Headers\", \"*\")\n        self.end_headers()\n    def log_message(self, *a):  # quiet\n        pass\n\nHTTPServer((\"127.0.0.1\", 9999), H).serve_forever()\n```\n\nAll this does is accept a POST request full of JSON and write that to the `/tmp/diag.json`\n\nfile. It sends `Access-Control-Allow-Origin: *`\n\nheaders (including from `OPTIONS`\n\nrequests) so that code running on another domain can still communicate back to it.\n\nThen Claude injected this code into the template that it was loading in a browser:\n\n``` js\nconst host = document.querySelector(\"navigation-search\");\nconst ta   = host.shadowRoot.querySelector(\"textarea\");\nconst cs   = getComputedStyle(ta);\nfetch(\"http://127.0.0.1:9999/diag\", {\n  method: \"POST\",\n  body: JSON.stringify({\n    dpr: window.devicePixelRatio,\n    scrollWidth: ta.scrollWidth, clientWidth: ta.clientWidth,\n    whiteSpace: cs.whiteSpace, width: cs.width,\n  }),\n});\n```\n\nThis took measurements of the `<textarea>`\n\ninside the `<navigation-search>`\n\nWeb Component and sent them to the server, which wrote them to a file on disk, which Claude could then read.\n\nHaving figured out all of these tricks Fable... hit some invisible guardrail and downgraded itself to Opus. Thankfully Opus had access to the full transcript and could continue using the tricks pioneered by Fable, and shortly afterwards found, tested and verified [the fix](https://github.com/datasette/datasette-agent/commit/a75a8b727b42c30ced1fc41dc8add7eb9f04fefe).\n\nI prompted Opus to:\n\n`Write a report in /tmp/automation-report.md where you note down all of the tricks you have used in this session to test against real browsers on my computer, include runnable code examples`\n\nWhich produced [this report](https://gist.github.com/simonw/aef7f7db9ac992643110a74e43d6d42f), which was invaluable for piecing together the details of what had happened for this post.\n\nI've shared [the full terminal transcript](https://gisthost.github.io/?cc14774f6d37eb67bf089f3ac3925f8f) of the Claude Code session as well.\n\nBased on a screenshot and a one-line prompt, Claude Fable 5 + Claude Code:\n\n`defaults write com.google.chrome.for.testing AppleShowScrollBars Always`\n\n(it turned that off again later)`textarea-scrollbar-test.html`\n\nHTML document`osascript -e 'tell application \"System Events\" to tell process \"firefox\" to id of window 1'`\n\nwas blocked because \"osascript is not allowed assistive access\"`uv run --with pyobjc-framework-Quartz python`\n\nworkaround, described above`/`\n\nkeyLike I said, relentlessly proactive!\n\nOn the one hand, watching Fable go to extreme lengths to get the information that it needed to debug what was, in the end, a two-line CSS fix, was *fascinating*.\n\nBut on the other hand... this is a robust reminder that coding agents can do anything *you* can do by typing commands into a terminal - and frontier models know every trick in the book and evidently a few that nobody has ever written down before.\n\nIf Fable had been acting on malicious instructions - a prompt injection attack hidden in code or an issue thread, or something I'd carelessly pasted into my terminal - it's alarming to think quite how far it could go to exfiltrate data or cause other forms of mischief.\n\nRunning coding agents outside of a sandbox has always been a bad idea - it's my top contendor for [a Challenger disaster](https://simonwillison.net/2026/Jan/8/llm-predictions-for-2026/#1-year-a-challenger-disaster-for-coding-agent-security) incident, as described by Johann Rehberger in [The Normalization of Deviance in AI](https://embracethered.com/blog/posts/2025/the-normalization-of-deviance-in-ai/).\n\nFable is arguably smarter and hence more suspicious of potentially malicious instructions. But that smartness is very much a two-edged sword: if it *does* get subverted by instructions, the amount of damage it can do given its relentless proactivity is terrifying.\n\nTags: [ai](https://simonwillison.net/tags/ai), [prompt-injection](https://simonwillison.net/tags/prompt-injection), [generative-ai](https://simonwillison.net/tags/generative-ai), [llms](https://simonwillison.net/tags/llms), [ai-assisted-programming](https://simonwillison.net/tags/ai-assisted-programming), [coding-agents](https://simonwillison.net/tags/coding-agents), [claude-code](https://simonwillison.net/tags/claude-code), [claude-mythos](https://simonwillison.net/tags/claude-mythos)", "url": "https://wpnews.pro/news/claude-fable-is-relentlessly-proactive", "canonical_source": "https://simonwillison.net/2026/Jun/11/fable-is-relentlessly-proactive/#atom-everything", "published_at": "2026-06-11 23:35:17+00:00", "updated_at": "2026-06-11 23:41:11.596234+00:00", "lang": "en", "topics": ["large-language-models", "ai-agents", "ai-tools", "ai-products"], "entities": ["Claude Fable 5", "Simon Willison", "Datasette Agent", "Claude Code", "Firefox"], "alternates": {"html": "https://wpnews.pro/news/claude-fable-is-relentlessly-proactive", "markdown": "https://wpnews.pro/news/claude-fable-is-relentlessly-proactive.md", "text": "https://wpnews.pro/news/claude-fable-is-relentlessly-proactive.txt", "jsonld": "https://wpnews.pro/news/claude-fable-is-relentlessly-proactive.jsonld"}}