Proving code is dead before you delete it

On the evening of 2026-08-12, three commits removed what was left of this site before Svelte:

  • 18:25 07af320 removed handlers/auth.py, login code from 2014.
  • 20:05 228414a removed templates/ and static/: 28 files deleted, 13 screenshots moved, 5,381 lines gone.
  • 20:38 0548a35 removed the template_path setting and the nginx /static/ alias.

Deleting is the easy part. Anyone can run git rm. The work is the argument that it is safe, and that argument is made of things that are not there. It is worth looking at how it was put together, because absence is a different kind of claim from the ones tests usually make.

Three findings, in order

Nothing loads handlers/auth.py. The route table never registered its Login or Logout handlers, so the app's login_url="/login" pointed at no handler at all. The file's own contents made the case stronger. It queried MongoDB (M[db].users.find_one), which the app has never depended on. It rendered login.html, which was not in templates/. And it has no import lines. The first line of the file is class Logout( AuthHandler):, so loading it raises NameError: name 'AuthHandler' is not defined. The commit that added it in 2014 was called stashing some reference code, and that is what it was: a fragment, not a module.

With auth.py gone, nothing renders a template. The app still had template_path="templates" in its settings, which is a string, not a reference. What uses a template is a self.render() call, and after the first commit a search of every Python file for render( came back empty. That finding depends on the first one. auth.py held the last render() in the repository, so templates could not be proven dead until it was gone.

Nothing asks for /static/. No Tornado route served it, and nothing in the built Svelte app requested it. But nginx did serve it:

location /static/ {
    alias /opt/pearachute/static/;
}

So "dead" meant unreferenced, not unreachable. Until that alias went, the server would hand a copy of jQuery from 2014 to anyone who asked for /static/jquery.min.js. A search can prove that nothing in the repository links to a path. Only the access log can say whether anything outside the repository still does, an old blog post or a bookmark. That is the edge of what this kind of evidence covers, and it is worth saying out loud when you delete something public.

Absence is a different kind of argument

A test proves presence: this request returns that page. A search proves absence: no file anywhere says this name. The two fail differently. A test that passes is evidence about the thing it ran. A search that comes back empty is evidence only about the places it looked.

That is why the useful version of the search crosses every place a reference can live, and why tools mostly will not do it for you. A dead-code checker reads one language's imports. The references that mattered here were in four different languages: a Python string setting (template_path), a Tornado route table, an nginx alias, and <img> tags inside Markdown files. Nothing that reads only Python would have connected static/docimgs/ to docs/united-federal.txt.

One more trap, from the same evening. The message on 228414a says it "also drops the now-unused template_path setting and the nginx /static/ alias". It doesn't. Those edits were made in the working tree and never staged, and 0548a35 carried them in 33 minutes later. The commit message described evidence that the commit did not contain. Check the tree, not the message, including your own.

Age is not evidence

It is tempting to treat old as dead. Here is every file in the tree on the morning of the deletions, placed by the date it was last edited:

Every tracked file at 07af320^, the commit before the first deletion, by the date of its last non-merge edit. docs/ (44 files of unserved writing, kept by policy) is left out. Hover a dot for its path.
The 44 deleted, moved and kept files as a table
filelast editedverdict
static/d3.v3.min.js2014-01-16deleted
static/jquery.min.js2014-01-16deleted
static/queue.js2014-01-20deleted
static/pperfect.js2014-01-31deleted
static/doc.css2014-05-14deleted
static/marketmagic.js2014-05-16deleted
handlers/auth.py2014-05-29deleted
static/tonalpohualli.js2014-05-29deleted
static/logo.png2014-11-01deleted
static/three.min.js2014-11-01deleted
static/threedemo.html2014-11-01deleted
static/threelogo.html2014-11-05deleted
templates/legacy.html2016-08-27deleted
static/logo.svg2016-09-08deleted
static/pforex.js2017-01-31deleted
static/css/normalize.css2017-05-04deleted
static/css/skeleton.css2017-05-04deleted
static/redditreader.js2017-05-04deleted
templates/ga-snippet.html2017-05-05deleted
static/js/homepage-logo.js2017-05-08deleted
static/images/favicon-16x16.png2017-05-17deleted
static/images/favicon-32x32.png2017-05-17deleted
static/images/favicon.ico2017-05-17deleted
templates/index.html2017-05-17deleted
static/loading.js2022-12-06deleted
templates/hello.html2025-10-24deleted
templates/market_magic.html2025-10-24deleted
templates/pitch_perfect.html2025-10-24deleted
templates/tonalpohualli.html2025-10-24deleted
static/docimgs/putty_download_1.jpg2011-01-21moved to docs/imgs/
static/docimgs/putty_download_2.jpg2011-01-21moved to docs/imgs/
static/docimgs/putty_download_3.jpg2011-01-21moved to docs/imgs/
static/docimgs/putty_start_1.jpg2011-01-21moved to docs/imgs/
static/docimgs/putty_start_2.jpg2011-01-21moved to docs/imgs/
static/docimgs/putty_start_3.jpg2011-01-21moved to docs/imgs/
static/docimgs/putty_start_7.jpg2011-01-21moved to docs/imgs/
static/docimgs/putty_website.jpg2011-01-21moved to docs/imgs/
static/docimgs/writingmapreduce.png2014-01-22moved to docs/imgs/
static/docimgs/logging-gone-wrong.png2014-06-07moved to docs/imgs/
static/docimgs/united-federal-newsletter.png2014-06-07moved to docs/imgs/
static/docimgs/cabinhiring.png2014-07-28moved to docs/imgs/
static/docimgs/blekko_v_duckduckgo.png2014-11-12moved to docs/imgs/
handlers/reddit.py2017-05-05dead, still here
web/src/lib/Counter.svelte2023-01-02dead, still here

The dates overlap almost completely. Age is a decent prior: most of the "everything else" row was touched during the July 2026 rebuild, and most of what got deleted had not been touched since 2017. But it is a prior, not proof, and both ends of the chart show why.

The youngest dead files were nine months old. On 2025-10-24 a coding bot added market_magic.html, pitch_perfect.html and tonalpohualli.html to templates/, with handlers on master that rendered them. Three months later the Svelte merge kept the svelte side's server, which had no handlers for those pages, and kept the files anyway. They were dead for 196 days before anyone deleted them. Sorting by age would have saved them.

Some of the oldest live files are older than most of the dead ones. handlers/__init__.py came in on 2014-05-29, in the same commit as auth.py, stashing some reference code. It is an empty file. Twelve years later auth.py was deleted and __init__.py is the package every handler on the site lives in. The demo datasets under /data/ were last edited in January 2023 and are served on every visit to two demo pages. Sorting by age would have deleted them.

Moved, not deleted

Thirteen screenshots in static/docimgs/ were just as dead by the serving standard, and they were moved to docs/imgs/ instead of deleted, because old write-ups in docs/ show them:

<img src="/static/docimgs/united-federal-newsletter.png" class="img-responsive">

That is the third outcome, and the easiest to miss: dead here, wanted somewhere. Running the search again for this note turned up two footnotes to it.

Only nine of the thirteen are referenced, from three files in docs/. The other four (cabinhiring.png, logging-gone-wrong.png, blekko_v_duckduckgo.png and putty_start_7.jpg) are not named anywhere in the repository. They moved because their directory moved. That was a reasonable call, but it was a call, not a finding, and by the standard the rest of the evening used, those four were dead.

And the nine references have outlived their target. Those <img> tags still say /static/docimgs/, which no longer exists. Nothing breaks, because docs/ is not served. The references that justified keeping the files now point nowhere.

When the answer is keep it

handlers/reddit.py is dead by exactly the same standard. Nothing imports it and no route uses it. It is kept on purpose, and the reason is written down where an agent or a person will find it: the repo's instructions call it "kept as reference from an earlier project".

That is the right shape for a keep decision. But run the same check on it anyway:

$ python -c "import handlers.reddit"
AttributeError: module 'tornado.web' has no attribute 'asynchronous'

It uses @tornado.web.asynchronous, which Tornado removed in 6.0, in 2019. It also passes a str to md5(), which Python 3 rejects. It is reference code that cannot load on the stack it sits in. That doesn't make keeping it wrong. It makes the note incomplete. A keep decision should say what the code is for, and also what it no longer does.

And then there is web/src/lib/Counter.svelte, the demo counter that ships with the Svelte starter template. It arrived on 2023-01-02 with stock svelte starter kit, nothing has ever imported it, and it is still in the tree. Nobody decided to keep it. It passed every review by being too small to notice, which is a different thing from being kept.

The checklist

What made this evening's deletions safe, as steps:

  1. Search every place a reference can live, not just one language's imports: route tables, template names in strings, server config like nginx, the built output, documentation, and if the thing was public, the access log.
  2. Delete in dependency order, so each finding rests on the one before it. Templates could not be proven dead while auth.py held the last render().
  3. Know which kind of dead you have. Unreferenced is not unreachable. A public path can be dead in the repo and alive on someone's bookmark.
  4. Check the tree, not the commit message. Including your own.
  5. For anything you keep, write down why, next to it, and run it once in a while to see if the reason still holds.

The deletions were three commits and about two hours. Most of that time was spent showing that nothing would notice.