servicenow-skills: what I got wrong and what I rewrote
A postmortem on the servicenow-skills project. The abstraction I ripped out, the scope I cut, and the moment I realized a rewrite was cheaper than patching.
There’s a commit from a Tuesday in October where I deleted 1,400 lines and the project immediately got better.
I remember because I sat with my finger hovering over the keyboard for a full minute before I ran rm. I’d spent three weekends on those 1,400 lines. They worked. They also made me dread opening the file.
This is the follow-up to the origin-story post. The first version of servicenow-skills did what I wanted. Then I kept building, and a few things went sideways in ways I wasn’t honest with myself about until much later.
The thing I thought would be hard and wasn’t
Talking to ServiceNow. That was my “this is going to ruin me” item at the top of the list. I’d had too many bad afternoons with that API — the weird 200s that are actually errors, the fields that accept writes and quietly drop them — and I assumed the client layer would be the worst part of the build.
It wasn’t. It took an afternoon. Once I stopped trying to be clever and just wrapped the fetch calls with a retry, a decent error type, and a “tell me exactly what you sent and what came back” log line, the thing shut up and behaved. I’d built it up as a boss fight when really it was a chore.
The thing I thought would be easy and wasn’t
Diffing.
I thought “check if the thing already exists, and see if anything changed” was a Saturday afternoon. Reader, it was not. ServiceNow returns fields in shapes that don’t match what you sent. Strings come back as "true" instead of true. Reference fields are sometimes sys_ids and sometimes display values depending on which endpoint you hit. Empty fields are sometimes "", sometimes null, sometimes just missing from the response.
I spent two weeks getting diffing to stop waking me up with false positives. At one point I had a spreadsheet — an actual spreadsheet — of field-type-to-normalization mappings. I’m not proud of the spreadsheet. I am proud that it’s gone now.
The abstraction I built, then ripped out
Okay, the 1,400 lines.
I’d built this thing I called the “operation graph”. Every skill describes its work as a DAG, the framework topologically sorts it, handles dependencies, rollback on failure, the whole beautiful declarative pipeline.
On paper it was gorgeous. In practice I was the only user, I had maybe six operations per skill on a bad day, and the graph was almost always just… a list. A list pretending to be a graph. I’d written a scheduler for a workload that had no scheduling problem.
The moment I knew it had to go was when I tried to add a new skill and spent ninety minutes fighting the graph validator instead of writing actual ServiceNow logic. The abstraction had become the project.
I replaced it with a for-loop. (It’s still a for-loop. It’s fine.)
The scope I cut
I had big plans for a web UI. A little dashboard where you’d see your skills, click one, watch it run against an instance, see the diff, approve or reject. I mocked it up in Figma one Sunday and got excited.
I never built it. That’s the right call — I just didn’t know it at the time.
The honest truth is I live in the terminal, Claude lives in the terminal, and the only person who was going to use the UI was me pretending to be someone else. Every hour on the dashboard was an hour not spent on the hard part: making the skills themselves trustworthy. Cute dashboards don’t matter if the thing underneath is lying to you.
The rewrite moment
“Rewrite vs patch” usually gets treated like a big architectural call. For me it was embarrassingly small. I was adding catalog items — the gnarliest artifact type by a wide margin — and realized I’d have to thread a new concept through every layer of the codebase to make it fit.
I sat there for a bit. Then I opened a new branch called tiny-core and spent a Saturday rewriting the core in about 300 lines. No graph. No rollback framework. No clever plugin system. Just: here’s an artifact, here’s what it should look like, go make ServiceNow match.
Monday morning I deleted the old version. It stung for about ten minutes and then it was gone and I felt lighter than I had in weeks.
I’m still finding things I got wrong, which I think is the correct state for a project you actually use. The one I’m circling right now is how I handle secrets across instances. That’s a post for a different Tuesday.
-D