Working With AI: A Concrete Example Carson Gross, creator of hyperscript, describes how AI helped him diagnose a parsing regression in the 0.9.91 release but failed to provide a correct fix, illustrating the Sorcerer's Apprentice problem where developers become overly reliant on AI and lose the ability to solve problems independently. I am ambivalent towards AI. It has become a very powerful tool for development but also comes with many dangers, both for us individually e.g. the slow dulling of our intellects as well as collectively e.g. environmental concerns, increasingly expensive personal computing, etc. In “Code is Cheap er ” https://htmx.org/essays/code-is-cheap/ , I warn about The Sorcerer’s Apprentice problem, where a developer becomes reliant on AI and is unable to understand and properly address issues that come up in the systems they are building. In this article I want to go through a specific interaction that I had with AI while maintaining hyperscript https://hyperscript.org to demonstrate the dangers of AI in general and The Sorcerer’s Apprentice problem in particular. For some background, hyperscript is an alternative interpreted scripting language for the web. It is, ironically, written entirely in JavaScript https://github.com/bigskysoftware/ hyperscript/blob/master/src/ hyperscript.js . It is a strange piece of software: I intentionally broke many of the rules of parsing when writing it as an experiment to see how things would work out. Some examples: It is not an approach I would recommend for most programming languages, but it has worked out pretty well for this project. Yet another demonstration that there are indeed multiple ways to skin the cat in software. Our story begins when a user reported a regression when upgrading to the 0.9.91 release. The following expression no longer parsed properly: fetch {% url 'trade:get symbol data' %}?symbol=${symbol} as JSON In particular, the as JSON was binding too tightly and trying to convert the string literal into JSON before it was handed to fetch instead of doing what the user expected and what it did previously namely fetching the given url with the results treated as JSON. This sort of binding conflict is a classic problem in parsing. Because hyperscript is an xTalk https://en.wikipedia.org/wiki/HyperTalk Descendants of HyperTalk style language and inherits the ambiguities of English, this problem is all the worse. The first thing to do was to investigate why this regression occurred. This is an area where I am typically going to lean on AI to help. I use Claude, and it did an admirable job finding the root cause: in 0.9.91 I had been overly aggressive in refactoring the go https://hyperscript.org/commands/go/ command to reuse logic in the fetch https://hyperscript.org/commands/fetch/ command. I had extracted a common method for both of these commands to use, parseURLOrExpression , but, in doing so, I accidentally expanded the grammar after the fetch command to include expression . Now, the as keyword has a meaning in expressions: it is a conversion expression https://hyperscript.org/expressions/as/ , allowing you to convert between types: set x to "42" as Int But the as keyword is also a modifier of the fetch command, telling it how to convert the response: fetch https://hyperscript.org as Text Perhaps this fact makes you throw up a little bit in your mouth. Good. The crux was that, inadvertently in the refactor, I had made the parser parse an expression after a fetch keyword which was now consuming the as keyword as an expression, rather than allowing it to be a modifier. With the help of Claude I was able to figure this out in a few minutes, dramatically faster than if I had had to do so on my own. So AI was very helpful in finding the cause of the problem. In fixing the problem, however, it was much weaker. I will admit I was being lazy and asking AI for a solution, so complaining about those solutions feels a bit, well, lazy, but I think the string of events is informative, so let’s go through what happened. The first suggestion that was given was to parse what is called a “string-like” leaf first, then fall back to a full expression: return this.parseElement "stringLike" || this.requireElement "expression" ; This fix would have solved the immediate problem presented by the user. However, it was very specific to the reported bug and wouldn’t have fixed the general case, such as if someone uses a variable as the target of a fetch: fetch $url as JSON I rejected this proposal because of this: too hacky and not general enough. The hyperscript parser has plenty of organically supplied hacks in it, so this may have been the pot calling the kettle black. The second proposal was interesting: add a noConversions flag on the parser, set it around the URL parse, and have AsExpression.parse bail when it is set: // AsExpression.parse if parser.noConversions return; This will horrify many parser engineers because it makes the hyperscript parser context-sensitive https://dl.acm.org/doi/epdf/10.1145/362686.362695 . Good. The hyperscript parser was already context-sensitive. However, in looking at this fix and thinking for a second, I realized that we already had the hacky context-sensitive infrastructure we needed without introducing a new flag on the parser. In the hyperscript parser we have a notion of “follows” https://github.com/bigskysoftware/ hyperscript/blob/ea9a6534d24cf5c7257adcaad75ee75b0c612d8e/src/core/tokenizer.js L11 , that is, tokens that are claimed by a “higher up” parse element as a follow token. The hyperscript parser is a somewhat strange recursive descent parser, and this allows a parse element usually a command to “claim” a keyword, and expressions won’t match against them during parsing. As an example, the when https://hyperscript.org/features/when/ feature uses or as a separator