The agent hit a missing skills index in the first minute and offered to build one. We told it to go ahead. That was the last straightforward decision we 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 our 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.
Four clones, four versions of the truth
Each of the four local clones had a .claude/skills/ directory. They had started from the same scaffold and diverged over months as we patched one without touching the others. Skill files had different names for the same operation. Hook references pointed at paths that no longer existed. CLAUDE.md files described 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 and flagged 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 we had never known existed. Each repo worked fine in isolation, and we had never compared them directly with a shared index in mind.
The startup scaffolding disagreed too
With the skills index settled, we 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. There was no error and no explanation for why the two sessions behaved differently.
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 we kept tracing it to the wrong source, because the error message never pointed at the hook.
The JIRA base URL was a placeholder
When /sync tried to pull session summaries and check their state, we 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. We had been reading session summaries for months without clicking through, because the IDs looked plausible. What the summaries were producing was unusable evidence. The field itself, once found, was a one-line fix.
Reading the version from a tag
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 we 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.
We 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, and merge commits are structural facts in the history that do not move.
The safeguard behind the frozen /item
The last thing we changed that day did not fix anything. It disabled a safeguard.
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 we gave up and killed the process.
For weeks we had assumed the problem was in /item itself, some bug in the close logic we 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.
What sat unnoticed for months
A drifted config, a placeholder URL and a safeguard with no timeout can sit in a working system for months without raising an error, and the symptoms get blamed on something else. Ask whoever runs your tooling whether every environment starts from the same checked configuration, whether the links in generated reports actually open, and whether each automated step at the end of a job has a time limit.