The agent hit a missing skills index in the first minute and offered to build one. I told it to go ahead. That was the last straightforward decision I made that day.

The /sync skill was supposed to be a simple warm-up: pull a shared skills index, confirm hook configs were current across my four local platform repos, check the dev board wasn’t stale. One command any Claude Code session could call to put itself in a known state before real work started. The agent’s first discovery, that no single canonical skills index existed, turned out to be the first of several.

The repos had four versions of the truth

My four local clones of the platform each had a .claude/skills/ directory. They had started from the same scaffold and diverged over months as I patched one without touching the others: skill files with different names for the same operation, hook references pointing at paths that no longer existed, CLAUDE.md files describing a tooling layout the repos had since outgrown. None of it was broken badly enough to surface as an error. It was just inconsistent, and inconsistency at the skills level means the agent behaves differently depending on which clone it starts in.

The agent built the index, flagging conflicts as it went rather than picking one version arbitrarily. Clone two had a skill that clone three had superseded under a different name. The base clone had a hook configuration that predated and partially overlapped what the other three repos were using. Half a dozen conflicts surfaced that I had never known existed, because each repo worked fine in isolation and I had never compared them directly with a shared index in mind.

The startup scaffolding disagreed across clones

With the skills index settled, I ran a pass through the rest of the startup scaffolding: env templates, mcp.json, and the CLAUDE.md at the root of each repo. All four had drifted. Some env templates still pointed at defaults from a layout that had been restructured months earlier. The mcp.json files had diverged enough that a session starting on the base clone loaded a different tool set than the same session starting on clone four, with no error and no explanation for why the two sessions behaved differently.

This matters because sessions inherit everything from startup hooks. If a hook on one clone reaches for a path that only exists on that clone, the session either fails silently or throws an error that looks like it came from whatever the session was trying to do. That error pattern had been showing up for months, and I kept tracing it to the wrong source, because the error message never pointed at the hook.

The JIRA base URL was wrong in the reference config

When /sync tried to pull vault session summaries and check their state, I noticed the JIRA ticket links in those summaries were broken. They pointed to an internal host instead of the actual Atlassian host. The summaries looked correct because the ticket IDs were formatted right and appeared in the expected places. The links just went nowhere useful.

One field in the JIRA reference config, the base URL, had never been updated after the initial integration setup. It was still an internal host, a placeholder from an early planning doc that predated the real JIRA instance. Every session that had ever pulled from that config had been generating broken links, appending valid ticket IDs to the wrong domain. I had been reading session summaries for months without clicking through, because the IDs looked plausible. What the summaries were actually producing was unusable evidence. The field itself, once found, was a one-line fix.

Tags were not where the version was

The /sync skill needed to report each clone’s current deployed version so session summaries could flag whether a given clone was current. The agent’s first pass read the latest git tag. That worked until I tested it against a repo that had just shipped a hotfix.

Hotfix releases tag master after the merge, but the tag sometimes lands on the merge commit and sometimes on the commit immediately before it, depending on which tooling ran the release flow. Reading the tag gave back a SHA with an unclear position in the branch history. The version number looked plausible, and a plausible version with an ambiguous lineage is exactly the kind of answer that fails silently at the worst possible time.

I rewrote the lookup to read merge commits directly: find the most recent commit on master whose message starts with “Merge branch ‘hotfix/”, then parse the version from the branch name. It is less elegant than reading a tag, but tags are labels someone attached after the fact; merge commits are structural facts in the history and they do not move.

The safeguard was why /item looked frozen

The last thing I changed that day was not a fix. It was a disable.

A check-learnings hook fires at the end of every /item session. Its job is to review what happened, extract anything worth keeping, and propose additions to the shared learning doc, so nothing useful gets lost when a session closes. The intent is sound. The problem was that the hook called an endpoint that could take up to 20 seconds to respond, and the /item close sequence had no timeout. When the endpoint was slow, the close just hung. The session looked frozen from the outside, and nothing moved until the endpoint finally responded or I gave up and killed the process.

For weeks I had assumed the problem was in /item itself, some bug in the close logic I hadn’t caught yet. The symptom was too consistent to ignore but showed up irregularly, roughly one session in three, which made it hard to reproduce cleanly.

Disabling the check-learnings hook fixed it in under a second. The hook has been off since then, waiting on a rewrite that adds a proper timeout.

The lesson it was meant to preserve was getting lost anyway, because /item never finished.