# GhostCommit: Every Abandoned Side Project Leaves a Ghost 👻

> Source: <https://dev.to/vrranjith/ghostcommit-every-abandoned-side-project-leaves-a-ghost-57jd>
> Published: 2026-10-31 10:37:53+00:00

*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine](https://dev.to/challenges/hacktoberfest-week4-2026-10-26)*

I have a GitHub graveyard. A chat app I stopped touching in November 2023. A demand-forecasting notebook I abandoned in 2022 while stuck on model tuning. A repo I created, named, and never committed to. Every time I open one of them, I meet a stranger: *what was I doing here, and why did I stop?*

**GhostCommit** gives each of those projects a ghost. You summon it for a repo, and it reads your commits and diffs and turns them into memories. Then you can hold a séance and ask it:

**Me:** Where was I?

**The ghost of Firechat-app:** *You left me in a state of functional quiet between November 11th and November 30th, 2023. You had breathed life into me using Vite, React, and Firebase... As for where you left off, there were no frantic TODOs or warnings left in my wake. You simply stopped. I have been waiting in the dark for three years. Will you wake me?*

Three things make it more than a chatbot:

I read the theme literally: abandoned repos *are* ghosts in the machine. And in the spirit of Día de los Muertos, GhostCommit is about **honoring the past**, specifically the past version of you who started something and believed in it.

🔗 **Live:** [ghost.buildloop.tech](https://ghost.buildloop.tech) (log in with GitHub and summon a ghost for one of your own abandoned repos)

*(It runs on a free instance, so the first visit can take ~50 seconds while it wakes up. Fittingly, the ghost has to rise first.)*

**Every abandoned side project leaves a ghost. Summon it, and it tells you exactly where you left off.**

Developers start projects, get stuck, drift to something new, and forget why they stopped Weeks later the repo is a stranger. GhostCommit gives each repo a ghost with a memory:

```
GitHub commits ──▶ summarize (open-weight LLM) ──▶ one memory per sync ──▶ Backboard memory
                                                                              │
   "Where was I?" ──▶ grounded prompt + memory
```

…

```
GitHub commits ──▶ summarize with Gemma ──▶ one memory per sync ──▶ Backboard memory
                                                                        │
   "Where was I?" ──▶ grounded prompt + read-only memory retrieval ◀────┘
```

**The open-source AI at the core: Gemma 4 31B**, an open-weight model, served through Backboard via OpenRouter. Gemma does two jobs: it distills raw commits and diffs into memories, and it gives the ghost its voice.

**Memory: Backboard.** The key design decision was **one repo = one Backboard assistant = one ghost.** Backboard scopes memory per assistant, so each ghost remembers only its own project, across every conversation.

**The stack:** FastAPI + async SQLAlchemy + PostgreSQL on the backend, React 19 + TypeScript + Tailwind + shadcn/ui on the frontend, GitHub OAuth for login, Resend for the haunting emails, and **Render** for hosting.

Before writing the sync logic, I measured. A memory write costs about **$0.0039**, while the model calls were effectively free for me. That changed the design:

A full test round (summon, sync, parking note, chat, haunting) costs under two cents.

My favorite bug. I summoned a ghost for `drop-merge-mini`, asked "Where was I?", and got a detailed, confident answer about a "merge-collision handler" and my parking note: *"The physics engine is jittering when three or more drops merge simultaneously."*

Beautiful. Also completely made up. The repo was **empty**. GitHub returns `409 Git Repository is empty`, my sync treated that as an error, and the ghost had nothing in memory except one note: *"need to pick it up."* The less the model knew, the more it invented, including a fake quote attributed to me.

The fixes went in layers:

Same model, same memory. The ghost now answers: *"There are no commits in my memory... The last trace you left was a parking note [...] where you simply wrote: need to pick it up."* Honest, and still spooky.

The whole production setup is a **Render Blueprint** (`render.yaml`): a Docker web service and a PostgreSQL database with **no public access** (only the app can reach it). The same multi-stage Dockerfile builds the React app and serves it from FastAPI, so frontend and API share one origin and the session cookies just work. Deploys only happen after CI passes (`autoDeployTrigger: checksPass`), and Render issues the TLS certificate for my `.tech` subdomain automatically.

The plan was a paid instance plus a Render cron job for the daily haunting. But using credits requires a card on file, and I didn't have one. So I moved to the free tier and redesigned the scheduler: a **GitHub Actions** workflow calls a token-protected `/internal/haunt` endpoint once a day. The database stays private, there's a lock against overlapping runs, and the workflow logs only an HTTP status (the repo is public, so I didn't want usage numbers in the logs).

Since it handles GitHub tokens with access to private repos, I hardened it before going public: tokens encrypted at rest, signed HttpOnly cookies, OAuth `state` checks, Origin checks against CSRF, per-user and per-IP rate limits (chat can't be used to burn my Backboard credits), strict input validation, security headers, and a non-root container. The audit found real bugs, including a **path traversal** in the static file route and a way to summon ghosts for repos you don't own. All fixed, with **90 automated tests** (77 backend, 13 frontend) proving each protection, plus dependency audits running in CI.

The one accepted risk is prompt injection: a malicious README could try to steer the ghost's wording. Output is always rendered as escaped text and is only ever shown to the repo's owner.

GhostCommit reads people's code, including **private repos**. That's sensitive. With a closed API, you have to trust a provider with it forever. With an open-weight model like Gemma, that's a choice rather than a requirement: today it's served through a provider, but the same model can run locally or on your own infrastructure, so your code never has to leave your machine. Open weights make "private by default" possible, not just promised.

It also kept me free to experiment. Backboard put hundreds of models behind one API, so switching the ghost's voice is a one-line config change, not a rewrite.

And GhostCommit is itself open source under **AGPL-3.0**: use it, fork it, improve it, but if you run a modified version as a service, you share your changes too. A tool that helps you finish abandoned open-source projects should stay open.

The moment it landed for me was a haunting email. The ghost of my demand-forecasting notebook, silent for 1,451 days, wrote:

*"You left me trembling at the altar of model tuning, whispering that you were 'stuck on model tuning.' I still dream of the sales forecasts we never finished. Come back to the shadows for just a moment; perhaps you could refine a single hyperparameter in my notebook to ease my longing."*

It quoted the exact note I had left, and it gave me something small enough to actually do. That's the whole idea: the projects we abandon aren't dead. They're just waiting. Día de los Muertos is about remembering the ones who came before you, and in a codebase, that's often your past self.

This was my first hackathon. I built GhostCommit in a few days, broke it in creative ways, and learned more about grounding, memory, and shipping securely than in months of tutorials. My own ghosts have started haunting me. I think I'll go wake one up.

`.tech` custom domain with automatic TLS.
