Quilltap 4.8.1
Quilltap 4.8.1 Release Notes
Four corrections, and all four of them the same correction wearing different coats.
A house of this size runs on questions it asks itself constantly and quietly. Is anyone already in this room? Is this cabinet empty, or merely shut? Is that the key, or only a thing shaped like one? Have I actually looked everywhere I claim to have looked? Answers to such questions are cheap to assume and expensive to get wrong, and 4.8.1 is the release in which four of them stopped being assumed.
None of these were reported by a user losing data. All four were found on a single instance, in the course of investigating why a perfectly ordinary Docker container and a perfectly ordinary desktop copy had disagreed about whose house it was.
The Cellar, Entered Without Knocking
Quilltap has, for some time, possessed an admirable arrangement for preventing two copies of itself from writing to the same database at once. The instance lock is acquired before the database is opened; a second Quilltap, finding the lock held by a living process, declines to proceed and says so plainly.
It was an excellent arrangement, and the migration runner had never once been told about it.
Every read and every write in the application proper goes through the SQLite backend, which takes the lock as a matter of course. The migration runner opened its own connection and went straight down to the cellar. This is an unfortunate exception to make, because migrations are not incidental writers — they are the heaviest writers in the entire codebase, the ones that rewrite whole tables in a single sweep. The failure mode this permits is precisely the one the lock exists to prevent, granted exclusively to the one subsystem least able to survive it.
On the instance where this was found, a Docker container held the lock and a second Quilltap ran ten migrations against the same database regardless, rewriting 20,037 embeddings and 9,212 memory rows, and only then discovered that it could not open the database at all. It had done all the work, in the wrong house, and been turned away at the door on its way out.
The migration runner now acquires the lock before it opens anything, and refuses to run when another live process holds it. Acquisition is re-entrant within a process, so an ordinary single-instance startup behaves exactly as it always did and notices nothing. A migration that cannot obtain the lock fails startup rather than proceeding hopefully, which is already how the house treats a migration that fails for any other reason.
An Unanswered Question Is Not a No
The second correction concerns a confusion between two very different silences.
The repository layer, when a query fails, returns an empty list. This is a defensible convenience in most places and a catastrophe in exactly one: the check that asks whether this is a brand-new installation. "There are no characters here" and "I could not find out whether there are characters here" arrived at the seeding logic as the same value, and the seeding logic, being cheerful by disposition, read both as welcome, new user.
The consequences of that misreading are not small. A transient read failure on an established instance — another copy holding the lock, an iCloud file only half-materialised, any of the ordinary indignities of a synced data directory — could send a fully-populated house down the new-install path: default characters, a default embedding profile, an entire .qtap import laid over the top of years of work. On the instance where this was found, nothing was actually written, because the seeding writes failed for precisely the same reason the read had. That is not a safeguard. That is a coincidence, and coincidences do not scale.
Emptiness is now established with a strict count that raises the underlying error rather than swallowing it, and seeding is skipped entirely when the question cannot be answered. Counting also runs beneath row validation and the document-store overlay, so a character whose vault happens to be briefly unreachable no longer quietly subtracts itself from the total. Both the character check and the embedding-profile check had the flaw; both are fixed. Ordinary list queries keep their existing forgiving behaviour, which is where it belongs.
The principle is short enough to embroider on something: seeding must fail closed. An unanswerable question is not a yes.
Saquel Ytzama — The Spare Key That Opened Nothing
There is a particular species of dread reserved for the discovery that the key you have been carefully backing up for years is not, in fact, a key.
An instance has one pepper and one quilltap.dbkey file. All three databases — main, LLM logs, mount index — open with it. The documentation, however, described per-database key files, and described them in some detail. quilltap-mount-index.dbkey was documented and has never been written by any version of Quilltap; it simply did not exist. quilltap-llm-logs.dbkey was worse, because it was real. Exactly one code path wrote it — changing your passphrase — and nothing anywhere ever read it. Since the other two paths that write the main key file never touched that copy, an instance whose passphrase had been changed after setup could carry a file that looked every inch a valid spare while holding a wrapping several passphrases out of date.
That write is now gone. If your data directory contains a quilltap-llm-logs.dbkey, it is inert and safe to delete. Quilltap does not delete it for you; removing key material on a user's behalf is not a decision an application gets to make on its own initiative, however confident it happens to feel.
The more immediately dangerous half of this was the backup instructions. BACKUP-RESTORE.md and DEPLOYMENT.md both gave the wrong path for the .dbkey file — the key lives in the data/ subdirectory alongside the databases, and the documented commands pointed one level above it. The commands therefore copied nothing. They did not fail loudly; they simply had nothing to say. Anyone who followed the published procedure to the letter possessed no backup of the one file which, if lost, renders every database permanently unopenable.
The same wrong path appeared in the Docker restore steps, and the in-app help called the file .dbkey rather than quilltap.dbkey. All of these now give the real path, and the help page warns specifically about the failure mode of aiming a backup one directory too high, since that is a mistake which announces itself only at the exact moment it cannot be repaired.
Back up data/quilltap.dbkey. Please do check that you have.
The Inspector Who Only Read the Rooms He Had Keys To
The last item is the smallest and, in its way, the most instructive.
The project is spelled Quilltap. Since 2.9, an ESLint rule has flagged the doubled-t misspelling, and it has flagged it faithfully — in every file ESLint was ever given the means to open. The flat config supplies no configuration for .md, .json, .yml, or shell files, so eslint . skipped each of them with a mild "no matching configuration" warning that nobody was reading, and npm run lint went on returning green while the misspelling sat in docs/ in the plain light of day.
An inspector who reports the rooms he has inspected is doing his job. An inspector whose report is indistinguishable from a report on the whole house is a hazard, and the hazard grows in exact proportion to how much you trust him.
npm run lint and npm run lint:fix now run scripts/check-quilltap-spelling.mjs after ESLint. It sweeps every tracked file, and every new file not already ignored, using the same pattern the ESLint rule uses — both now read it from quilltap-spelling.js, so the two enforcers cannot drift apart while each believes it is speaking for the other. Deliberate occurrences are declared in an ALLOWED_PATHS list, or exempted line by line with a quilltap-spelling-exception marker.
The misspelling itself was corrected in one document, where it had reached the release asset names, the download URLs, and the prose alike. Every remaining occurrence in docs/ is deliberate: a document that states the spelling rule must quote the wrong spelling in order to state it, and the shipped changelogs record the misspelling being caught and fixed. Those are on the allowlist, and unchanged.
What Changed
- fix (Foundry): The migration runner now acquires the instance lock before opening its database connection and refuses to run when another live process holds it. Previously migrations — the heaviest writers in the codebase — used their own connection and bypassed the lock entirely, allowing a second Quilltap to rewrite whole tables while a first held the lock. Acquisition is re-entrant within a process, so ordinary single-instance startup is unchanged; a migration that cannot acquire the lock fails startup rather than proceeding. (bug 58)
- fix (Foundry): First-startup seeding now determines emptiness with a strict count that raises the underlying error, and skips seeding entirely when the question cannot be answered. The
find*family returns[]on any failure, so a transient read failure previously read as "no characters exist" and could send a fully-populated instance down the new-install path — default characters, a default embedding profile, and a full.qtapimport. Counting runs beneath row validation and the document-store overlay so a temporarily unreachable character vault does not subtract from the total. Both the character probe and the embedding-profile probe were affected and both are fixed; ordinary list queries keep their existing empty-list behaviour. (bug 59) - fix (Saquel Ytzama): Removed the write of a second key file at
quilltap-llm-logs.dbkey, the remnant of a per-database-key design that was never built. It was written only by the passphrase-change path, read by nothing, and never updated by the two other paths that write the main key file, so it could hold a stale wrapping while appearing to be a valid spare. Existing copies are inert and safe to delete; Quilltap does not remove them automatically.quilltap-mount-index.dbkeywas documented but never written by any version. An instance has one pepper and onequilltap.dbkey; all three databases open with it. (bug 60) - fix (docs):
BACKUP-RESTORE.md,DEPLOYMENT.md, and the Docker restore steps gave the wrong path for the.dbkeyfile — one directory above its real location indata/— so the documented backup commands copied nothing and left users without a backup of the one file that makes the databases unopenable if lost. In-app help called the file.dbkeyrather thanquilltap.dbkey. All paths corrected, with an explicit warning about the one-directory-too-high mistake. - fix (Foundry):
npm run lintandnpm run lint:fixnow runscripts/check-quilltap-spelling.mjsafter ESLint, sweeping every tracked and new-but-not-ignored text file. The ESLint rule only ever saw files ESLint parses, and the flat config supplies no configuration for.md,.json,.yml, or shell files, so those were skipped with a warning while lint stayed green. Both enforcers now read the pattern fromquilltap-spelling.jsso they cannot drift. Deliberate occurrences are declared inALLOWED_PATHSor exempted per line with aquilltap-spelling-exceptionmarker.
Installation
Desktop App
Download from the quilltap-shell releases page:
macOS:
- Download the
.dmgfile and open it - Drag Quilltap to your Applications folder
- Launch Quilltap from Applications
Windows:
- Download and run the
.exeinstaller - If SmartScreen warns about an unknown publisher, click "More info" → "Run anyway"
- Launch Quilltap from the Start Menu or desktop shortcut
Linux:
- Download the
.AppImagefile, make it executable (chmod +x), and run it - Or install the
.debpackage:sudo dpkg -i quilltap_*.deb
Node.js (any platform)
npx quilltapOr install globally:
npm install -g quilltap
quilltapOpen http://localhost:3000 in your browser. Requires Node.js 24+. First run downloads ~150–250 MB and caches locally.
Docker
docker pull foundry9/quilltap:4.8.1Or use the startup scripts:
# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/foundry-9/quilltap-server/refs/heads/main/scripts/start-quilltap.sh | bash
# Windows (PowerShell)
irm https://raw.githubusercontent.com/foundry-9/quilltap-server/refs/heads/main/scripts/start-quilltap.ps1 | iexA patch release about epistemology, which is not a sentence anyone hoped to write. The house now knocks before entering the cellar, declines to mistake a locked door for an empty room, no longer keeps a spare key that opens nothing, and has stopped confusing the rooms it inspected with the whole of the building.
If you take one action on the strength of these notes, let it be this: confirm that you have a copy of data/quilltap.dbkey, and that it is the file itself and not the silence where a cp command found nothing.
Installation
Desktop App (recommended)
The Quilltap desktop app (Electron) is available from
quilltap-shell 4.1.12.
Download the release for your platform (macOS, Windows, or Linux).
The quilltap-linux-arm64.tar.gz and quilltap-linux-amd64.tar.gz rootfs
tarballs attached to this release are used by the shell's Lima (macOS) and WSL2 (Windows) VM modes.
Node.js (any platform)
npm install -g quilltap
quilltapOn first run, the CLI downloads the application files (~150-250 MB)
and caches them locally. Subsequent launches start instantly.
Docker
docker pull foundry9/quilltap:4.8.1See the README for setup instructions.