An additional project for the Ghost in the Machine challenge. Ghostlight Archive remains my first Week 4 entry. The rules say only the first entry is considered for judging; this is a separate build, not another eligible entry.
Deleting a function looks decisive in a diff.
The name is gone. The tests pass. Somebody eventually closes the tab.
But the reason that function existed can disappear with it. Especially the deeply unglamorous reason: “please don’t remove this last retry; something actually succeeds there.”
I wanted a ghost with unfinished business. So I built Graveyard Shift: a small code-history séance where you open a deleted function’s grave, question it, inspect the records, and replay a returning bug. 🕯️
What I Built
There are two graves in the demo.
legacy_retry really disappeared during a rewrite. Its old body remembers a boundary condition that the replacement lost.
read_config is a less cooperative ghost. Challenge its grave and you discover load_settings, a living function with the same normalized body. The name disappeared; the implementation survived.
That second case matters. A tool that turns every deleted name into a dramatic obituary would be entertaining and wrong.
The history is an original, authored fixture, not a discovered incident in somebody else’s repository. Its four commits deliberately contain a bug, a fix, a rename and a rewrite. That makes the demonstration inspectable and repeatable without inventing a story about a real maintainer.
Demo
Open read_config first. Press Challenge this grave. The server re-reads the history and compares syntax-tree fingerprints. The tombstone changes when the exact-body match is found.
Then open legacy_retry. Ask what changed, inspect its source records, and press Replay the curse.
The page shows actual server states and elapsed time. The ghost moves, little spirits drift through the graveyard, and optional synthesized sounds respond to discoveries. Sound starts after a click; there is a quiet route and a motion-pause control. The questions still work without theatrical effects.
The hosted model runs on CPU, so the first question can involve model loading. A disabled button and a visible status explain what is happening. There is no fake typing timer pretending to be inference.
The bug has to come back before the haunting counts
The replacement loop uses range(attempts - 1).
With an attempt budget of three, that makes two calls. An operation that succeeds on its third call never gets there.
The ritual runs three real tests against that rewrite:
- success on the first attempt;
- success on the final permitted attempt;
- a failing operation consuming its full retry budget.
The first test passes. The final-attempt case errors, and the budget case fails its assertion. The process exits with code 1.
Then the runner takes the exhumed legacy_retry body from the observed history, adapts its function name to the current API, and runs the same tests in another fresh directory. All three pass; the process exits with code 0. ✅
The model does not get to announce that the bug is fixed. The test runner does.
Actual browser captures, edited with pauses shortened. The silent GIF is a walkthrough, not an inference-speed measurement. Sound is available in the app.
How I Built It
The extractor uses Git and Python’s AST module, which reads code structure without importing or executing the repository.
For each commit on the observed first-parent history, it records functions by file and qualified name. A disappearing key becomes a candidate grave. An exact normalized-body match under a new name supports a rename or move candidate.
“Normalized” here is deliberately narrow: remove location attributes and replace the function’s own name before hashing its syntax tree. A changed implementation will not match just because it looks similar. More complicated edits, copies and split functions can remain unresolved. 🔎
The hosted application exposes only its reviewed fixture. A separate read-only CLI can inspect local Python history. The public demo does not accept repository uploads or arbitrary code.
For conversation, I used Qwen2.5-Coder-1.5B-Instruct, pinned to an immutable model revision, with Transformers and PyTorch. The prompt receives facts and snippets from the selected function, deletion commit and current body. The exact records remain visible beside the answer. [1]
I initially tried the smaller 0.5B model. It omitted citations, copied a diff instead of explaining it, and once answered an unsupported question about the developer’s feelings with: “The developer did not feel angry.” It even attached a valid-looking [E1].
That is the important failure: an existing citation ID is not evidence that the sentence follows from it. ⚠️
The larger model also invented intentions. I changed the task instead: constrained generation lets Qwen select only E0, E1, E2 or E3. E0 means the record does not establish the answer. The app displays the selected record’s exact text and source, rather than publishing model-written prose.
That prevents an invented confession from becoming the answer. It does not guarantee that Qwen selects the most relevant record. The deterministic lineage check and test results stay separate. The ghost’s short greeting is scripted; the evidence selection is the real model call.
The application is served from a Hugging Face Docker Space on CPU Basic. I used Hugging Face instead of DigitalOcean; no DigitalOcean integration is claimed. [2]
Why Does Open Innovation Matter?
The model weights, inference code, history extractor and regression fixture are inspectable. I can change the prompt, replace the model, examine the loop comparison, or rerun the test without depending on an opaque explanation API.
That control was useful immediately: the first model was a poor fit, and the source/test machinery stayed usable while I changed the conversation layer.
The public demo still processes questions on its hosting server. It is not an offline privacy demonstration. Don’t paste private code into it.
The local history CLI reads a repository without calling an inference service. Its output can include sensitive deleted code, so that output should stay private too. Open weights make local deployment possible; they don’t make every hosted product private by default.
Code
The README includes local setup and the history CLI. The test runner executes only fixed authored code, with a stripped environment, temporary directories and a wall-time limit. Those measures are not an operating-system sandbox for untrusted repositories.
This is a bounded demonstration of two kinds of disappearance and one returning bug. It does not forecast function deaths, infer why a developer made a decision, or automatically prove regressions in arbitrary repositories.
Prize Categories
The project uses GitHub Actions to check its fixture, Python syntax and browser-script syntax. The challenge’s GitHub Copilot category explicitly includes GitHub Actions; this is the Actions route, not a claim that Copilot wrote the code. [3]
Qwen, Transformers, PyTorch, Git and Hugging Face do the other work. I’m not carrying over Ghostlight’s partner claims or listing tools that this project doesn’t use. The first-entry judging rule stated above still applies.
References and credits
- Qwen2.5-Coder-1.5B-Instruct model card and Apache-2.0 licence.
- Hugging Face Docker Spaces.
- Challenge prompt and partner criteria, contest-specific first-entry rule.
Application, miniature history, visual design and sound motifs are original. Screenshots and GIF show the actual application. Application and fixture source: Apache-2.0. Fonts: DM Sans and Space Mono, served through Google Fonts under their open font licences.
AI disclosure
AI was used to draft the article.


Top comments (0)