<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: runathicku</title>
    <description>The latest articles on DEV Community by runathicku (@runathicku).</description>
    <link>https://dev.to/runathicku</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4093332%2F52cabc17-f01b-426c-b111-4ee299028c05.png</url>
      <title>DEV Community: runathicku</title>
      <link>https://dev.to/runathicku</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/runathicku"/>
    <language>en</language>
    <item>
      <title>What I learned rolling out Claude Code across a company full of legacy systems</title>
      <dc:creator>runathicku</dc:creator>
      <pubDate>Tue, 25 Aug 2026 04:37:05 +0000</pubDate>
      <link>https://dev.to/runathicku/what-i-learned-rolling-out-claude-code-across-a-company-full-of-legacy-systems-4j1d</link>
      <guid>https://dev.to/runathicku/what-i-learned-rolling-out-claude-code-across-a-company-full-of-legacy-systems-4j1d</guid>
      <description>&lt;p&gt;I maintain the internal systems of a mid-size manufacturer in Japan: pre-framework PHP that predates Composer, VB.NET ClickOnce apps nobody has recompiled in years, and a shared Oracle schema whose only documentation is the schema itself. I've been running Claude Code against this estate daily for months, and rolled it out to colleagues.&lt;/p&gt;

&lt;p&gt;Modern-stack advice ("just run the tests!") doesn't survive contact with this world. Here is what actually worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Hooks are the only guardrail that actually holds
&lt;/h2&gt;

&lt;p&gt;CLAUDE.md rules are suggestions. Under context pressure - long sessions, compacted history - the model can drift from them. A PreToolUse hook cannot drift: it's a program that inspects every shell command before execution and forces a human approval prompt when it matches a dangerous pattern.&lt;/p&gt;

&lt;p&gt;Ours escalates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL write verbs reaching any DB CLI: INSERT/UPDATE/DELETE/DROP/TRUNCATE/ALTER through mysql, psql, sqlplus, sqlcmd - including piped .sql files, whose contents you can't see at hook time&lt;/li&gt;
&lt;li&gt;rm -rf, git push --force, git reset --hard, curl | sh&lt;/li&gt;
&lt;li&gt;service restarts and registry writes on Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mechanism is simple: the hook reads the tool call as JSON on stdin, regex-scans the command, and returns a permissionDecision of "ask" with a reason. Twenty lines of bash or PowerShell. The key design decision: ask, not deny - legitimate destructive work still happens, it just requires a human to say yes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Encoding is a one-way door
&lt;/h2&gt;

&lt;p&gt;The scariest incident we had involved no SQL at all. A Shift-JIS (CP932) source file was edited by a tool that assumed UTF-8. Every Japanese character became U+FFFD - and once saved, the original bytes were gone. No conversion recovers that; only a pre-damage backup does.&lt;/p&gt;

&lt;p&gt;If your legacy estate includes Windows codebases, you very likely have non-UTF-8 files (CP1252 counts). Two defenses:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A PreToolUse hook that blocks edits on any file that doesn't decode as strict UTF-8, and on any file already containing U+FFFD (previously damaged - editing entrenches it).&lt;/li&gt;
&lt;li&gt;A day-one census: list every source file that fails strict UTF-8 decoding, and decide deliberately, per file, whether to convert or keep the legacy encoding.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. "Dead code" isn't dead, and frozen paths beat trust
&lt;/h2&gt;

&lt;p&gt;In a codebase with no tests and no original author, the phrase "this looks unused" is a trap. Our rule: paths nobody currently understands go into a frozen-paths.txt. A hook lets Claude read and study them freely, but any edit requires human approval. As investigation turns unknowns into knowns, paths get unfrozen.&lt;/p&gt;

&lt;p&gt;This inverts the usual dynamic. Instead of hoping the AI is careful everywhere, you declare exactly where carefulness is mandatory, and the machine enforces it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The first deliverable is a map, not a diff
&lt;/h2&gt;

&lt;p&gt;The most valuable Claude Code sessions on a legacy system produce zero code changes. We run a phased, read-only investigation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Perimeter&lt;/strong&gt;: inventory, entry points (URLs, mains, cron), and exits (DB connections, file writes, network calls)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data map&lt;/strong&gt;: which tables does the code actually read/write, from which files - a table-to-code cross reference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Money paths&lt;/strong&gt;: end-to-end traces of the 2-3 flows that matter, verified against a real record&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk register&lt;/strong&gt;: what's still not understood (stays frozen), single points of failure, time bombs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every discovery goes into a NOTES.md. A session that only added knowledge is a successful session - and after a few weeks, the undocumented system has documentation again, written as a by-product.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Legacy landmines your modern-stack instincts will miss
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Y2K38 is today's bug&lt;/strong&gt;: 32-bit PHP overflows epoch timestamps past 2038-01-19. Certificate expiries and long-term date math hit this now. Route future dates through DateTime, never epoch integers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit code 0 lies&lt;/strong&gt;: legacy batch scripts swallow errors. Verify the artifact (file exists, is fresh, row count moved), not the exit code - especially for unattended jobs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session-dependent services&lt;/strong&gt;: a process started at login sees mapped network drives; the same process as a Windows service does not. Changing how something starts silently changes what it can see. Enumerate those dependencies before touching startup behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Byte-defined columns&lt;/strong&gt;: VARCHAR2(10) in bytes truncates multibyte text at 10 bytes, not 10 characters. Validate in bytes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;None of this requires anything exotic - hooks, a text file of frozen paths, and discipline about read-only investigation. If you'd rather not build it from scratch, I packaged our hooks, CLAUDE.md templates and playbooks into two small kits: a &lt;a href="https://runathicku.gumroad.com/l/cc-governance-pack" rel="noopener noreferrer"&gt;team governance pack&lt;/a&gt; and a &lt;a href="https://runathicku.gumroad.com/l/cc-legacy-kit" rel="noopener noreferrer"&gt;legacy survival kit&lt;/a&gt;. But the ideas above are the valuable part, and an afternoon is enough to implement them yourself.&lt;/p&gt;

&lt;p&gt;Questions about running Claude Code against genuinely old systems welcome - this is my day job.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>devops</category>
      <category>legacy</category>
    </item>
  </channel>
</rss>
