“The horror! The absolute horror!”
To find out more about the reality of all the (generally overblown) promises about LLMs, I have been doing a very serious experiment on the poster child for Generative AI: coding. I wrote one critical post, and I am close to writing up my conclusions, but I am hitting some work that I have to do first before I can publish that (e.g. create a demo video which is a lot of work, and before that I need to finish some final functionalities and improvements.
This is Lab Note #2. Some basic explanations about my setup are in Lab Note #1.
Context: As an experiment to learn about the limits of LLM-coding, I am building a professional visual modelling application from the ground up. It can handle multiple grammars (definable in JSON). It has state of the art graphics. It is rich in what users can influence and how (e.g. schemes, style/colour/geometry properties). It is responsive. It is well documented. It has a solid software architecture. Every line of code (and many of the other texts) have been generated by the LLM (so far almost exclusively Claude Code). It runs on macOS and Windows (Linux will be added later). If I finish it and I am satisfied, it will be open sourced. This is a serious sized project: 137k lines C++, about 15k lines stable documentation and 30k lines of temporary documentation (analysis, design and planning for substantial changes over the last half year), so about one third of what is in play is not code but documentation.
Warning: That I am doing this should not be read as either pro or contra coding with LLM-agents. I am highly critical of the hype, but I am trying to establish the limits of LLM-coding based on as serious a use case as possible. I can say already that my experience so far has let me conclude that LLM-agents will in no way be able to replace actual human engineers.
Something happened yesterday. While being halfway the phases of implementing a refactor of the undo/redo setup and approaching the end my limit, out of the blue my Codex limit got reset. Jay! With that new limit Codex (GPT-6 Astra xhigh) was able to complete the implementation this morning.
Short note what I was doing: I was busy getting Codex to implement an improved auto-layout in my visual modelling app, when I found out about an undo/redo issue (behaviour bug), behind which lurked an architectural issue: the implementation of undo/redo was very vulnerable to coding errors and needed a refactor to improve its structural robustness. In my book, I fix that architectural issue first instead of fixing the bugs that a lack of that architectural structure has made easy to appear. This is not something an agent will always do or suggest, they may happily keep on adding bug fix after bug fix. In this case, it was Codex that actually pointed out that this was an issue when I asked it to analyse the cause of the bug.
When Codex finally finished with the refactor today, a short manual test went fine. So, problem solved and move on, right? Wrong.
The next step I did was to let Claude (Opus 5.5 at xhigh) do a review. I could have let Codex do its own, but it is a bit of a hassle to create a copy where one review cannot influences another, so I let Claude do it. My prompt was:
> Our current branch holds autolayout work and a complete refactor of undo/redo based on bug findings in the original implementation. I want you to do a deep analysis, as deep as you can, on the work done. Focus on code quality, architecture quality, robustness, and performance. Write the result in docs/20260927-review.md
Let me first show you the summary of Claude’s review. If you’re not a software engineer, skim it and get the gist of it.
1. Summary
The core of the undo/redo redesign is sound. One capture point (the first write through a setter), exact qualified record identities, prepare-then-apply replay with stale-state preflight and post-apply recapture, explicit Applied/Unchanged/Refused outcomes, retained native objects instead of reconstruction, reachable/unreachable save points, and history invalidation by connected component together remove the class of bugs the old per-command undo data had. The branch deletes a large amount of hand-maintained before/after state (e.g. the merge undo structs in import_merge.h). The autolayout core is cleanly layered: backend-neutral descriptions, a validated bridge registry, composition and validation in core, an OGDF bridge behind an approved exception boundary, and atomic application with a preflight equality check.
The significant problems sit around that core:
- Forward view maintenance moved into the action finalizer and into landscape load, and its scope is derived from record kinds. Landscape- and model-level property edits fan out to full geometry maintenance of every view in scope, open or closed (P1, P2).
- The capture guarantee holds only while a thread-local journal is bound; unbound writes pass silently. Public-field structs and
voidsetters that drop refused writes make “single capture authority” a convention rather than an enforced invariant (A1, A3, A4). - The JSON restore helpers survive as a second, lenient apply path, used by autolayout, inside-view creation and one Qt command (A2).
- Autolayout composition has several superlinear loops and runs the solver synchronously on the UI thread; the performance test covers a flat view only (P5, P6).
- A residue of stale comments, error texts and history references from the replaced design, plus project style-rule violations in new code (C2–C6).
2. Findings overview
| ID | Sev | Area | Finding |
|---|---|---|---|
| P1 | High | Perf | Finalizer runs full view maintenance on every view touched by a landscape/model property record, including closed views |
| P2 | High | Perf | Landscape load now maintains every view; formerly only the displayed view at scene rebuild |
| A1 | High | Arch | Unbound writes to published documents succeed silently; divergence surfaces later as refused undo |
| P3 | Medium | Perf | Structural edits cost O(owner list) at seal, undo and redo |
| P4 | Medium | Perf | JSON history representation: ~17 KB retained per changed record |
| P5 | Medium | Perf | Autolayout composition: repeated full-view builds per scope, O(n²) helpers, O(k³) packing |
| P6 | Medium | Perf | Autolayout solver runs synchronously on the UI thread (up to 60 s time limit) |
| A2 | Medium | Arch | Lenient legacy JSON apply/restore helpers remain a second apply path |
| A3 | Medium | Arch | Capture coverage depends on caller discipline; public-field structs; hand-listed byte accounting |
| A4 | Medium | Arch/Rob | void setters silently drop refused writes; the action continues on half-applied state |
| A5 | Medium | Arch | Mandatory derived-state maintenance lives in qtgui, outside mechanical test reach |
| R1 | Medium | Rob | Throwing nlohmann accessors on undo payloads terminate instead of refusing |
| A6 | Low | Arch | Autolayout workspace duplicates live UUIDs; workspace models answer global delete requests |
| A7 | Low | Arch | Composition vocabulary spelled as string literals in five places |
| A8 | Low | Arch | Algorithm identity validated through a dummy bridge catalogue |
| A9 | Low | Arch | Autolayout fields optional on read although always written |
| A10 | Low | Arch | Non-allocating apply discipline has no failure mode to protect under no-exceptions |
| P7 | Low | Perf | Capture-side scans proportional to landscape size |
| R2 | Low | Rob | Error-level logs for ordinary outcomes; log flood after a refusal |
| R3 | Low | Rob | Silent defaults in restore (“blob predates the field”, default Point) |
| R4 | Low | Rob | Paste ignores autolayout-settings refusals |
| C1–C8 | Low | Quality | File size, stale texts, history in comments, naming, style rules, repetition, vestiges |
| T1–T3 | — | Tests | Coverage strengths and gaps |
Observations and Conclusions
So, on first sight, Codex did a good job with a decent refactor. But as a software engineer I am (initially, at least) horrified by the findings of Claude (and note: I have enough experience with Claude Code reviewing Claude Code code to tell you that this is not Claude Opus 5.5 being better than GPT-6. My initial use of reviews had Claude Code review what Claude Code had created and it made mince meat out of it…).
Many of the medium-rated problems are a disaster waiting to happen. Take A4: “setters silently drop refused writes; the action continues on half-applied state”. Say what?! So, the code can create half-applied states and simply move on? That is a hard no in my book (and in the setup/instructions I have given the agents).
Many of these findings I have to corroborate (they tend to be pretty reliable, sometimes a deeper inspection unearths that the review was wrong when you start to try to repair it). Let’s look at what Claude says about software architecture (which is key to good systems)
Claude dutifully reports the strengths of the approach that Codex created:
Strengths
- One capture authority at the storage boundary instead of per-command before/after data.
- Exact qualified identities (
landscape,owner,kind,object) and replay by identity, never by semantic search. - Prepare-then-apply replay: resolution, stale-state checks and capacity reservation precede the first write; post-apply recapture must equal the expected state.
- Retained native objects (
EACMP_RetainedDocumentRecord) preserve addresses and reverse registrations across delete/undo. - Save points with explicit reachability; history invalidation by connected component instead of wholesale clearing.
- Buffered document notifications delivered after completion; UI container lifecycle (close/open views, clear target model) moved behind committed notifications.
- Modal continuations re-resolve targets by UUID after every dialog.
- Canvas gestures own preview tokens, with stale-callback protection (
view_gui.cpp:150–173) and a re-entrancy guard (:267–299). - Autolayout: neutral description vocabulary, validated registry, strict bridge-result validation, lock closure, atomic application with preflight, core/bridge separation.
That is good, right? But now we return to finding A4:
A4 — Refused writes are dropped by void setters (Medium)
Setters such as EACMP_ViewElement::setPosition return void and skip the assignment when capture refuses. The refusal poisons the journal (view_undo_state.cpp:3446–3451) and the owner rolls the action back on completion, so the final state is correct. Until then the rest of the action runs on a mix of applied and dropped writes, Synchronous-topic subscribers can observe that state, and each further write logs another Error (R2). Callers cannot distinguish “written” from “dropped”.
I cannot stress how horrible this sounds to me. Each of these I have to investigate (together with the agent) and fix before I can move on. And the same thing that got me here can happen again: the fix may require yet another repair that enables the fix in the first place. A5 is another architectural issue, by the way, it is visual logic that has been implemented in the presentation layer, instead of in the visual logic layer.
This is quite representative of my experiences so far. My project lives at the edge of what brute force trial and error with token pattern statistics can do reliably and well enough.
So, what do we see here?
What we see is ‘statistics in action’. What we see is an approximation of the result of good coding, not good coding itself. We see a mix of success and failure. A chance of something good enough coming out of the agent (because with these kinds of defects, this is far from good enough, and we’re talking the top frontier models here in high effort settings).
And this holds for the reviews too. A review can say the architecture/code is good, but that isn’t reliable either. A Claude Fable 6.1 review from not long ago did not unearth what a Claude Opus 5.5 review did today. Again, the unpredictability/unreliability is a fundamental property of these massive, statistically constrained randomness-based systems.
The agent creating the code has the same capabilities (or more) than the one making mince meat of that same code. Of course we can massively increase the amount and roles of agents so they all work together in a mesh, more or less ‘checking’ each other. That will of course help, but try to imagine the explosion of token use and the economic unviability that comes with that and the likely diminishing returns when you scale that up. In the symbolic-AI era we already had ‘combinatorial explosion’ as the problem that was unsolvable. It really does seem we are having another version of that in the current era too. So let’s repeat it once more: this technology is astronomically unlikely to support ‘recursive self improvement’ of AI models themselves and thus open up the AGI/ASI trajectory that is being dangled before a scared humanity (next to that there are other reasons why this is likely impossible with digital technology)
In the meantime, I now have another 23 issues to investigate and get fixed. This kind of situation isn’t new for me and it is an illustration of my personal experience over the last half year that coding with LLMs slows down more, the further you get. This, of course, is generally true, but still, my feeling is that I am getting slower as I progress, not just because of complexity, but because the further I come, the more costly fixing the poor parts from the past (that only get clear now) becomes.
Hmm, maybe I’m not so close to my conclusion post after all. First fix this.
First post: Anthropic/OpenAI may be spending more than $1000 for every $100 you pay them
Previous Lab Note #1: Serious coding with LLMs. Lab notes 2026-09-25: Codex vs Claude Code, Usage Limit Resets (contains basic info on the project)
[You do not have my permission to use any content on this site for training a Generative AI (or any comparable use), unless you can guarantee your system never misrepresents my content and provides a proper reference (URL) to the original in its output. If you want to use it in any other way, you need my explicit permission]