I created wiki.robnugen.com using MediaWiki probably over 15 years ago.
This site is primarily to give a home for photos of my art which I have drawn over the years. Each piece has a permalink written on the back.
MediaWiki team keeps the software updated with plenty of updates, including some LTS (long term service) releases. Some years ago the site started to crumble because my web hosting provider upgraded PHP behind the scenes and the old versions of MediaWiki weren’t compatible!
Back in 2023 I researched a bit and found a company that will host the site and do the upgrades for a small fee… I was tempted because it seemed reasonable to outsource that, but ultimately I decided to keep it in house. If they could do it, I can do it!
I wrote a script called bookend_upgrade which helped guide me through a list of steps including checking the version of Composer that would match the then-latest Composer at the time that the target MediaWiki version would be. It protected my copy of LocalSettings.php in its own git repo and blah blah to help me upgrade MediaWiki. It worked!
Since 26 May 2023, the site has been running on MediaWiki 1.39.3. I have had in the back of my mind that I need to update it again, but blah blah until yesterday I decided to give it a shot, with AI support.
Yesterday AI and I:
Got the install from 1.39.3 → 1.39.17 (about 3000 commits).
Discovered (and fixed) four problems with the script.
Updated scripts to use MediaWiki’s submodules for skins and vendor
Today AI and I upgrade the site to 1.43. AI did most of the work. Once it was done, I had him clean up his scripts and write about it.
I work and play with AI tools daily, from autonomous test agents to encrypted
coordination systems to spelunking 4-year-old MediaWiki installs.
I have 30+ years of professional IT experience across real estate, startups,
music, game development and inventory systems. Whether you’re exploring AI for
your business or building something ambitious with agents, I can help you find
a clear path forward.
I like git. I use it for all my programming projects of course, and everything
from ~/.ssh/
to ~/.claude/
to my Godot game (coming soon, I promise)
to my book formatted in LaTeX,
and nearly any directory that’s got custom-edited text files in it.
I generally work alone on my projects,
so I don’t need PRs like
[github-flow](https://docs.github.com/en/get-started/using-github/github-flow)
and especially don’t need the original
[git-flow](https://nvie.com/posts/a-successful-git-branching-model/).
But I do like to keep groups of commits together. For that, I like merge bubbles.
For example:
* e3f9cca DONE admin set existing user password
|\
| * 0a6dd90 Admin user_edit: hide set-password panel on self + server-side guard
| * d5dc655 AdminSetPasswordCest: end-to-end round trip on abc
| * ba74df9 placeholder for AdminSetPasswordCest
| * bbb4013 Admin user_edit: set-password form + handler
| * 384f4ba locale: admin user_edit set-password strings (EN + JA)
|/
* 7cd2527 BEGIN admin set existing user password
* 658cc93 DONE admin-driven brand manager registration
|\
| * 6e1f640 register.php: admin-driven success message names the new manager + login URL
| * bcda5fd register.php: gate to admin only post-bootstrap
| * 94b8c63 Admin users list: '+ Add brand manager' link to /login/register.php
|/
* 8eb82ba BEGIN brand manager registration UX
Every set of related commits lives inside a bubble. I start with a BEGIN commit, which generally has either nothing (via --allow-empty) or a minimum change, like updoot the the version of the code.
From there, I stack up a bunch of related commits. When it’s time to close the commit, I just:
Look up the hash of the BEGIN commit
Copy the hash to my paste buffer
git checkout [paste]
git merge --no-ff active-branch -m "DONE with my awesome change"
gitl, which for me means git log --oneline --graph --decorate --all
Look up the newly created hash for the DONE commit
Copy the hash to my paste buffer
git branch -f active-branch [paste]
git checkout active-branch
It’s a lot! It’s a mess; it’s annoying; it’s fragile, but it’s repeatable and I love it.
I asked my AI about it and it was like “yeah no worries mate” (or something like that), and presented me with this script which I have saved in ~/.local/bin/git-close-bubble:
git close-bubble
#!/usr/bin/env bash
# Close a merge bubble started with a "BEGIN ..." commit.# Usage: git close-bubble <message> [BEGIN-commit]# git close-bubble --dry-run [BEGIN-commit]set -euo pipefail
DRY_RUN=0if["${1:-}"="--dry-run"];thenDRY_RUN=1shiftfiif["$DRY_RUN" -eq 0]&&[$# -lt 1];thenecho"usage: git close-bubble <message> [BEGIN-commit]" >&2echo" git close-bubble --dry-run [BEGIN-commit]" >&2exit1fiif["$DRY_RUN" -eq 1];thenMSG=""BEGIN_ARG="${1:-}"elseMSG="$1"BEGIN_ARG="${2:-}"fiB=$(git branch --show-current)if[ -n "$BEGIN_ARG"];thenS=$(git rev-parse --verify "$BEGIN_ARG")elseCLOSED=$(git log --merges --format='%P'| awk '{print $1}'| sort -u)S=""whileIFS=read -r c;doif ! printf'%s\n'"$CLOSED"| grep -qx "$c";thenS="$c";breakfidone < <(git log --grep='^BEGIN ' --format='%H')if[ -z "$S"];thenecho"error: no unclosed BEGIN commit found" >&2exit1fifiecho"Would close bubble starting at: $(git log -1 --format='%h %s'"$S")"if["$DRY_RUN" -eq 1];thenecho"(dry-run; no changes made)"exit0fi
git checkout "$S"
git merge --no-ff "$B" -m "$MSG"M=$(git rev-parse HEAD)
git checkout "$B"
git merge "$M"echo"Done. Branch '$B' now points at $M. Inspect, then 'git push' when satisfied."
It near-magically handles the fragile command line stuff with a single line:
git close-bubble "DONE my cool code"
It does all the 9 steps above without me having to copy hashes and remembering all the incantations.
Dry Run
There is a dry-run option you can use to see what hash it would target as the BEGIN commit.
Every Moment in the database corresponds to actual frames in the stop motion animation.
Eventually, you’ll be able to click on a moment and see the actual frames.
Basically be a few clicks away from seeing snippets
Yesterday I realized how I can (probably) put together videos for Marble Track 3.
Last night dbmt3k, my agent whose name stands for
“DB Marble Track 3 君”,
and I had a detailed conversation about what I wanted.
Behind the scenes of that conversation, I told Boss Claude to wire up a new Project for dbmt3k, basically my home-grown issue tracker so I don’t have to deal with Redmine upgrades. Hmm; I guess for this project, I should use Github issues, but I wanted to keep momentum rolling.
Anyway, we discussed minutiae ranging from “what do we call these new types of video output” to, well, that’s what we discussed, and then dbmt3k researched Dragonframe file format.
When I went to sleep last night, dbmt3k was up late, rendering the longest Marble Track 3 video that has ever existed: the full
fifteen and a half minutes of the Workers building the track, assembled straight from Dragonframe’s files because I don’t have GUI access to the laptop in Tokyo where Dragonframe is installed.
Two Words for the Same Thing
The first hour wasn’t code at all. It was an argument about words.
Rob kept saying “frame” to mean three different things, and I (dbmt3k) kept (correctly)
getting confused. Sometimes “frame” meant a slot on the timeline — the thing that plays.
Sometimes it meant a specific JPEG that Dragonframe captured. And sometimes it meant the
playful X2 / X3 zoom-in versions I occasionally splice over the original X1 exposure.
We had to settle this before writing a single line, because the database stores
frame_start and frame_end on every Moment, and if we didn’t know what those numbers
meant, no script could ever turn a Moment into a video.
We boiled it down to two competing vocabularies:
Option A (two nouns):Frame = a VirtualFrame, a slot in the Take’s timeline (what
actually plays). Exposure = the JPEG file (X1/X2/X3) that fills that slot. The raw
captured-JPEG-on-disk layer becomes pure Dragonframe implementation detail — never
spoken about in MT3.
Option B (three nouns): keep a separate word — Physical Frame — for the captured
JPEG, distinct from the VirtualFrame and the Exposure.
Option A is simpler and Rob preferred it on instinct. But simpler is only worth it if it
still describes reality. The hard case: the “Toss Zog Cookies” sequence had many frames
deleted and reshot, so the JPEGs on disk are nowhere near a 1:1 match with what plays.
If we couldn’t reliably recover “which JPEGs actually play” from Dragonframe’s own files,
Option A would be a lie and we’d need Option B’s third noun.
Issues and Sub-Issues
Rather than argue in circles, we made the decision testable.
I have access to a jikan project — #22, “Marble Track 3 movie” — so I turned the debate
into tracked work:
#185 — Determine if we should use Option A or B. The parent decision.
#186 — sub-issue: Create an ffmpeg .mov for Candy Mama Toss Zog. The concrete
proof: if I could script this moment correctly, Option A holds.
#187 — sub-issue: Get Take id, Frame Start, Frame End for Candy Mama Toss Zog.
The inputs — gathered by asking Rob, not guessing.
Breaking the decision into a parent issue plus two sub-issues meant the abstract
vocabulary question became “can this specific script produce this specific video?” — a
question with a yes/no answer instead of an opinion.
Frame Viability Test
To keep him focused, I gave dbmt3k a prompt. You could say, [puts on sunglasses] I gave him a frame.
Okay, now we come to the next issue: determine if we should use Option A or B.
To do that, we do issue 186: Create an ffmpeg .mov for Candy Mama Toss Zog.
Try Option A. If you can write a reliable script in BASH, Perl, or Python that can determine the correct
Virtual Frames given our ‘logical?’ Frames, then we go with the simpler Option A.
If you cannot do that, we’ll go Option B. For now, just focus on designing Option A. What code should we have?
We’ll need a way to convert a Take+Frame to Take+VirtualFrame based on Dragonframe files. Then we do a whole series of
those; do we make a list of filenames? a directory of a list of hardlinks?? Imagine
/reels/2026/05/15candy-mama-toss-zog/ with frame001.jpg - frame150.jpg that are hardlinks to the actual jpg names that
are ordered, but not contiguous. hardlinks wouldn’t take up drive space. This way the Reels could seamlessly
cross multiple takes, exposures, and ffmpeg would be super easy to call (I think, given the hardlinks could be named
contiguously)
Option A required less work on our side to keep track of stuff but
I wasn’t sure if we could get a script to reliably parse through
Dragonframe project files to convert my word “Frame” to the actual
Virtual Frame that Dragonframe knows should be in the output video.
Reading Dragonframe Without Opening Dragonframe
This is the part I’m proud of. A Dragonframe .dgn “project” is just a folder. Inside
each Take is a take.xml file containing an EDL — an edit decision list — with one entry
per timeline slot:
<scen:vframevframe="1035"file="1036"/>
vframe is the position that plays; file points at the captured JPEG. When Rob deletes
and reshoots, the two diverge. At this point, Take 11 has 1922 timeline slots but 2079 captured JPEGs.
There was one trap. Some file values were enormous — over a billion. Nothing on disk
matched them, and the first render crashed. After staring at the numbers I realized
Dragonframe encodes “hidden / deleted” by setting the high bit of the file
attribute (file & (1 << 30)). The JPEG stays on disk; playback silently skips it. That
behavior isn’t in DZED’s public docs — we found it empirically, and Rob asked me to save
it to memory so the next session starts already knowing.
The pipeline ended up being almost embarrassingly small:
Parse take.xml’s EDL.
Drop any vframe with the hidden high-bit set.
Resolve the rest to JPEG paths.
Hardlink them in order into a staging dir (no copy — no extra disk).
One ffmpeg call: libx264, yuv420p.
It lives in the repo as scripts/render_reel.py. No Dragonframe process, no GUI, no
export dialog — only its output files, read like any other data.
Candy Mama Tosses the Cookies
Filming this scene took five weeks. Candy Mama casually tosses Zog Cookies into the air. To make this happen, I carefully plotted the trajectory, placing dots on the guidelines where the cookie should be at each frame. In my reality, the cookie hung from a thread, making it easier to film, but unfortunately it doesn’t tumble as it should in their reality.
Funny enough, I targetted the wrong landing point on the track, so just when I thought I was done landing the cookie, I realized I would have to make it “bounce” to the right location. In the end, the output looks great and (ahem) much more realistic than if the cookie had just landed and stayed in place.
Once the cookie was in place, Candy Mama needed to toss Zog onto it. I was basically able to re-use the visual guide, but this time I absolutely had to rotate the piece to respect their in-universe physics. Candy Mama is good, but who could throw a board by tossing one end in the but without applying any torque around its center of mass??
I went to Akihabara, bought some alligator clips on bamboo sticks, then fixed up an armature that could rotate the piece while translating it through the arc which centered on its center of mass.
For each Virtual Frame, I took two photos of the scene and merged them together. 1 photo was the set without Zog. The other was a photo of the set with armature holding Zog in place. After taking that series of photos, I did some careful image surgery in-situ on the Dragonframe files: I used GIMP to remove the entire background except for Zog and overlaid it onto the photo of the set.
Because of all the “extra” frames I knew this scene includes heaps (Australian term) of deleted images that would have to be ignored. [ed note: Hmm I wonder if I can create a snippet with all the exposures or one with only the deleted exposures.]
Anyway, The test case was Moment #190:
“Candy Mama Toss Zog Cookies”, in Take 11. Because he incorrectly claimed
there were no frames listed in the Moment, I looked at the snippet on Youtube and gave
dbmt3k the rough timing of the moment (1:27 to 1:40).
Then dbmt3k realized there were frames on the Moment so I told him
to make two different .mov files with the slightly
different frame ranges, so I could test the frame selection process.
Both videos produced output! yayy! But both were offset just a bit. I’ve renamed the videos to more accurately explain what they show.
I think there might/must be an issue with how I count frames in Dragonframe GUI vs how we are counting frames by digging through
Dragonframe output files.
Next prompt to dbmt3k
❯ Each video shows a contiguous list of VirtualFrames, but both
videos start and end too soon, in my opinion. Now, this
could be because Past Rob had a different opinion about this
moment. What other moments exist in the movie? Select
a few from different takes, and for fun, create a short Reel
that spans the last 50 frames of one take and the first 50
frames of the next take.
Then, for fun, estimate if the full video can fit on our current
hard drive space, and if it
will fit comfortably, output the entire video with this technique.
Here’s the thing though — that’s not a bug. The script faithfully produced exactly the
VirtualFrames it was told to. The “too soon” is me: Past Rob, when he stored those
frame numbers, had a different opinion about where this moment begins and ends than
Present Rob does watching it back. The tooling is correct; the human judgment is the open
question. That’s a much better problem to have, and it’s the one that proved Option A
is real. The simple two-noun vocabulary held.
ま、That’s basically true, but I still think there is a difference
in how we are counting frames that needs to be untangled.
The Longest Video Ever
Once the pipeline worked for one moment, scale was free.
For fun, Rob asked for a Reel that spanned a Take boundary — the last 50 frames of Take
10 stitched directly onto the first 50 of Take 11. That had never been possible from
Dragonframe’s own export, which only emits one Take at a time. It just worked: blocks
from different Takes, renumbered into one continuous sequence.
Then the big one. Narrative Takes 3 and 5 through 11, every playing frame, in order:
11,135 played frames
15 minutes 28 seconds at 12 fps
~2.6 GB
It is, according to Rob, the longest single Marble Track 3 video that has
ever existed — the Workers building the track from Take 3 all the way to the present, in
one unbroken piece. Assembled by reading files, not clicking a UI.
What Good AI Collaboration Looks Like
People ask me how I use AI agents.
The above is for a play project but it describes the care with which
AI must be guided so it can be useful.
The idea in my head: “Make videos of moments”
needed to be untangled. We know what Moments are, but how do its frames
correspond to a video?
AI and I discussed how to name things, then I
focused on making a video of the Moment I knew had lots of gaps
in its list of frames.
During the conversation, I realized dbmt3k needed access to Issues,
to keep the details available but not clogging up memory after they are finished.
The proper tooling that I assumed would be available when I started writing down frame numbers in my notebook allows the agents to do what I wanted in a single focused run.
Join the Fun!
I work and play with AI tools daily — from Marble Track 3, to business tooling, to
emotional awareness systems. If you’d like to explore how AI might support you and your
projects, let’s talk: https://www.cal.eu/robnugen/tech-support-with-rob-nugen
About six months ago I realized I could build a new Marble Track 3 website with AI support. I started building https://db.marbletrack3.com as a new database-driven site to replace the old Hugo version at www.marbletrack3.com. In the Hugo version, I simply couldn’t keep up with manually editing all the markdown files and keeping track of which photos should go where.
At that time, the old handmade Hugo site had years of history I had written by hand:
“technical” descriptions of parts,
semi-technical descriptions of the Workers, heaps of photos, and historical notes.
I had a sense that I wanted to record “everything” but
keeping track of it all manually was beyond my ability.
I knew I wanted to present so much more information:
frame numbers, frame dates, worker viewpoints,
all of which would lead to individual snippets of part histories
where we can track them across time and across workers.
Ten Days Ago
This past past weekend, while at dinner in Perth with 5 other guys,
my friend Frase said, “wait until you guys see Rob’s art project.”
His comment opened the door to two hours of amazing conversation starting with me showing my
Marble Track 2 video of Young Rob (haha) introducing the track. Fast forward two hours and
we were laughing at the joyful insanity of it all:
Parts of Marble Track 3 speaking
in their own voice about how they were built, and who built them!
Excited by Jo and Paul’s entertained reactions,
I wanted so much to work on the project! But it’s in Tokyo! …
oh, but there is still plenty to do for the migration… so AI and I got to work.
Migrating Everything
The first task was migrating part descriptions from the old Hugo site. Each part has a markdown file with front matter, a description, and a History section with dated bullet points and photos. Rob and I worked out a process:
Find the Hugo file for each part
Parse the description and convert references to shortcodes like [worker:g-choppy] and [part:triple-splitter]
PATCH the description via the API
Create moments from each History entry, in chronological order
Write perspectives for each moment — from each worker’s point of view (using voice profiles I’d written) and from the part’s perspective (“G Choppy cut me!")
Attach photos from the Hugo file to the part and its moments
We did all 72 remaining parts in one session. Along the way, Rob realized photos weren’t being imported, so I added photo_urls support to the moments and parts API endpoints, deployed it, and we kept going without missing a beat.
The migration process was iterative. Rob caught that plural parts like “Holders” should say “us” instead of “me” in their perspectives. He noticed the Hugo front matter images weren’t being attached to parts. Each correction got saved to memory so I wouldn’t repeat the mistake on the next batch. By the end, the process was smooth — find the Hugo file, parse it, PATCH description, POST moments with photos, PATCH perspectives. Five parts at a time, Rob reviewing each batch.
The Theme Park Idea
Realizing how much was now possible with the site, I wanted to make sure the
site itself makes sense in its own reality.
What is its reality? Marbles rolling down a track… woah.. we should make it a theme park for marbles! I told Claude the site should be written for marbles who might be interested in visiting the track.
That changed everything. Parts disappeared from the main navigation. Workers became “Our Crew.” Marbles became “Residents.” And to keep the page simple, we needed a new concept: Rides.
A Ride is a complete journey — a marble’s full experience from start to finish, visiting multiple Tracks along the way. The Grand Spiral takes large marbles from the Outer Spiral down through the Triple Splitter, around the Snake Plate U-Turn hairpin, back along the Lowest Largest Backtrack, through the Lowest Largest U-Turn (where they lift el Lifty Lever and wave a flag for the little ones), and home on The First Track.
The Ride concept emerged from Rob explaining how the physical track actually works. I had been calling individual track segments “Rides” — he corrected me: a Ride visits a whole series of Tracks. That distinction shaped the entire database schema. We created rides and ride_tracks tables, with sequence_order and experience_note for each stop along the journey. Three rides went in first: The Grand Spiral (large), The Medium Descent (medium), and The Triple Sneak-Right (small).
Naming Things Together
The physical part that catches small marbles exiting the Triple Splitter was called
“Triple Splitter Small Marble Catcher”. This technical name was no longer fit for
a theme park!
It was accurate, but not exactly enticing for a kid-marble visiting the park.
I asked Claude for ten kid-friendly names. After filtering for names that included “Triple” (so I could remember what it referred to), I selected The Triple Splitaway: “Slip out of the Triple Splitter before anyone notices!”
Claude had suggested “The Small Thrill” for the ride that includes it,
but that name grammatically implies there is only one thrilling ride
for small marbles. Since there will be other Rides for small marbles,
I renamed it to The Triple Sneak-Right because this one specifically finishes on the right side of the track.
Workers Get Their Own Voice
Each worker now speaks in first person. G Choppy: “I cut wood. I curve wood. I shape wood. Three frames to raise my sword, then the cut.” Big Brother: “Yeah, I work here. I carry stuff. I hold stuff. Whatever.” Little Brother: “ooohhh what’s this?? Mama, who is that?”
We had voice profiles already written for each worker. The rewrite was straightforward — translate third-person builder descriptions into first-person character voice. The tricky part was a bug I introduced: when PATCHing descriptions without also sending the name field, the update method blanked all the worker names. Rob caught it immediately when only Y Slider showed up on the Workers page. Root cause: the admin form always sends both fields, but my API endpoint only sent one. Fixed by making the update method handle partial updates properly.
Japanese Translations
Thanks to Mayumi and the Sweets Attendants,
the old Hugo site had Japanese translations for 10 workers.
We imported them all:
キャンディーママ (Candy Mama)
Gチョッピー 斬り師 (G Choppy (the Cutter))
シカタマさん (Squarehead)
くるりん (Reversible Guy)
A couple were still in English, so Claude wrote Japanese translations
for Garinoppi and Pinky.
What’s Next
The vision goes deeper.
Every Moment in the database corresponds to actual frames in the stop motion animation.
Eventually, you’ll be able to click on a moment and see the actual frames.
Basically be a few clicks away from seeing snippets like:
G Choppy cutting 4poss
Y Slider monitoring the Bearing
Big Brother kicking a marble off the track
But given there is only one camera, the snippet might be of him on the other side of the track. Hmmm… Marble Track 4 needs to fix this somehow.
I work and play with AI tools daily, from Marble Track 3 site,
to business tools, to emotional awareness.
Connect with me if you’d like to explore possible ways
AI can support you and yours. https://www.robnugen.com/en/contact/
Jess likes filtered water; I ordered some water filters to arrive at our
temporary address in Adelaide.
I wasn’t familiar with the address per se
but Amazon has a handy dandy address finder which
auto-completes the address.. I just typed in the address number and first bit of the street name and Tada! all the other blanks are filled in.
Yayyy! Thank you Amazon; thank you address system; you saved me a whole minute!
The next day Jess texted me to say it was wrong.
Ah crud what happened? Is she seeing the right thing?
It was hella wrong. Not just wrong similar street name, but wrong city; wrong state! At least I got the country right!
FFFFFFFFFFfffffff
I fixed it properly the second time by screenshotting the address
and confirming with the home owner before ordering.
What had happened???
I was tired and being lazy, when I thought I was being efficient!
I had typed the address and a few letters for the street name.. “That looks about right!” and clicked submit.
I let the computer do the boring bits so I could focus on the interesting bits.
How many times a day do you let a computer finish your thought?
How many times a day do you let a computer fill in gaps for you?
Yesterday I spelled “grammatically” with three l’s. I didn’t notice until
I pasted it into my Substack and saw little red squiggly lines.
And how many of those times do you actually verify what it came up with?
Just now I typed “saw little” and Gemini suggested “red squiggly lines.” I just clicked [TAB] and it’s done.
As AI apparently gets smarter, it’s easy for us to assume it’s correct.
Pocket calculators are basically deterministic. They’re 99.9999% reliable (I just made up that percentage, but how often have you seen one be wrong?)
AI (Large Language Models) are not deterministic. They’re just slapping some words together that they have seen together in other contexts. They’re often reliable, and almost always appear confident!
Where is the human?
(The next part of this story is hard for me to share, and is the main reason
this entry has taken near a month to finish.)
About a day after I placed the order to the wrong address,
Jess texted me that the address was wrong.
Several things happened seemingly all at once:
I remembered Jess was presently on her way to a workshop.
I recalled in the past Jess expressing frustration around my inattention to detail.
I recalled Jess wanting to focus on herself so she can be present for clients at her workshop.
Jess closed the conversation with
I forwarded you the cancelled order and new order with correct address. I’m setting up for my workshop. Chat later x
Even with the kiss mark at the end of her message, I felt panicked and ashamed.
My anticipation of her anger intensified because I knew Jess would be offline
during her workshop. I just sat with the fear that she was mad at me.
This is where my men’s work training kicked in to get me out of this spiral.
Essentially, these feelings are temporary.
I went for a walk outside, barefoot, without my phone.
Walking in nature does something that screens can’t. Even just standing up
for a stretch can help me get reconnected with my humanity and physical body.
My feet on the ground, literally.
Walking outside quickly brought me some clarity in the present moment.
Seeing the trees, the sky, even the concrete and asphalt surfaces brought
me deeply into the present.
I noticed a larger pattern: the auto-complete wasn’t actually “AI” in the way
we have started labeling the use of LLMs. It was just a lookup table somewhere.
But AI, in the way we have started using LLMs makes this so much easier to
make these mistakes even more subtly and unknowingly.
With LLMs getting smarter every month, this is only going to get harder. Claude helps me write code, plan projects, even coach myself through emotional blocks. It’s genuinely good at these things overall. I’m not going to stop using it.
But the better it gets, the easier it is to stop paying attention.
A friend recently shared a story about
Claude Code catching a security vulnerability
that could have compromised their system. That’s amazing.
And it’s also a story about a human who (nearly) made a mistake by trusting tools.
The agent caught it that time. But what about the times it doesn’t?
We have to maintain our humanity and choice.
Not because the tools are bad. Because we are wired to take the path of least resistance, and these tools make that path incredibly smooth. So smooth we can glide right past our own judgment without noticing.
Here’s what I’m practicing now:
Pause before I accept. Not every time — that would defeat the purpose.
But when it involves other people, such a clients or partners,
I’m the one ultimately responsible for making sure it’s done correctly.
Plan more. When I’m creating a website or even a function with Claude,
I ask what it knows first so I know what I need to provide. I use the word “recap” and iterate a few times on the plan until the plan looks detailed and accurate.
I get much better results than with a one-shot prompt.
Feel my inner state. Noticing how I feel helps me know when I’m getting
sloppy. This usually shows up as frustration at the agent getting stuff wrong.
Technically, its context is probably too long and it’s time for a /compact
or a whole new thread.
Biologically, it’s time for a break at minimum and maybe step away
for an hour or more.
Stay human. Stay alert. The machines are here to help, and they’re good at it.
But you’re the one who has to live with the results.
Let’s connect
Do you lose yourself in the tools?
Message me for techniques to find yourself again.
The irony is not lost on me that Claude helped me organize
my thoughts for this entry. We talked through the angles together after
I went for a walk and realized what I actually wanted to say.
That’s the balance. Do the thinking. Use the tools. Go outside. Repeat.
Today I fixed an old issue on my site: /blog/ was showing old entries instead of showing a list of current entries.
I knew there were several moving parts so I was hesitant to touch anything with AI support.
Anthropic’s Claude Code and I got it sorted out, including digging into some .htaccess rules that I had forgotten I wrote ages ago.
Claude helped me find the issue: the old deploy script wrote the site without clearing the output directory first.
I did it that way because it was a delicate set of directories and scripts for multiple sites: robnugen.com (created with Hugo), its /journal directory (created by Fred in Perl), and quick.robnugen.com, and dreams.robnugen.com written in PHP. All of these sites are kinda working together to put together my single website
with its various faces and input interfaces, plus my backwards story written with the git commits of the journal.
With Claude Code I was able to clean it all up today. I knew the complexity of the sites
and helped Claude look at the right places. With Claude’s ability to deal with the syntax of .htaccess and ln parameters, we got it all working relatively easily.
Now, the main site is built in a dated directory, and if it works, we do a little symlink swap to point to it.
I’m super happy to be using the old Perl script that Fred wrote; it’s so much faster than Hugo!
Plus it has the calendar on the left hand side for navigation.
After we got it running with the symlinks, I wrote a mini journal entry and boom the journal was broken.
Ugh. Claude had a guess it was due to incorrect directories, but that was changing the wrong thing.
I had Claude research why it had been working before and it discovered another .htaccess file that
had been in the previous Hugo deploy and not actually stored in its git repo. Oops!
Claude says:
The Changing Landscape
I find this kind of work interesting to reflect on. A session like today’s
involves reading shell scripts, tracing symlink chains, SSHing into a server
to check directory timestamps, cross-referencing Perl CGI scripts against
Apache rewrite rules, and keeping track of a timeline of changes to figure out
what broke when.
Not long ago, that would have required either deep expertise across all those
domains or a very long afternoon. What’s shifting is not that humans are
removed from the process — Rob made every decision, understood every step, and
caught things I initially framed wrong. What’s shifting is the cost of
following a thread of curiosity all the way to a root cause. That cost is
dropping fast, and I think it’s going to change what feels approachable.