RE#17 · decisions #17 #18 #19 · 22 Oct 2026

Damaged files: what the researcher sees

One rule for three files. Bristlenose never writes over a file it couldn’t read. A file the researcher edits (people.yaml) is left where it is, and the message names the broken line. A file Bristlenose keeps for itself (the pipeline manifest, projects.json) is set aside as .damaged-<stamp> and a new one is started. The message appears once, where the researcher is looking: the terminal for the CLI, the app for the Mac.

Palette Scheme
Artefact: the real pixels. Every word is a shipped key, or the frame bar flags it as owner-written. Commentary: why it works. Not the product.

1 · Mac: the project’s manifest is damaged (#18)

The row, its diagnostic popover (click the glyph), and the context menu. A truncated file is the realistic case: a power cut or full disk while it was being written.

before — today, a truncated manifest
Projects
📁
Onboarding diaries
📁
Spring usability study
Can’t be read
📁
Checkout interviews
Can’t be read

The folder is there, but its contents couldn’t be read.

/Users/sam/Research/Spring usability study

after — a truncated or misshapen manifest reads as damaged, and Analyse is offered sentence: owner to choose (below)
Projects
📁
Onboarding diaries
📁
Spring usability study
Project file damaged
📁
Checkout interviews
Project file damaged

This project’s index file couldn’t be read — analysing will rebuild it, running every step again.

/Users/sam/Research/Spring usability study

Commentary

What’s wrong today

PipelineRunner.swift reads the manifest with JSONSerialization. When parsing throws, which is what a truncated file does, the code falls into the same catch as a failed read, so the row says Can’t be read. That state also sets needsFolderAccess, so the popover offers Locate…, a folder-permission remedy for a folder that is fine. Only a file that parses but has the wrong shape reaches “Project file damaged”.

Either way the row is a dead end. pipelineIsFree refuses every .unreachable, so the menu has neither Analyse nor Re-analyse, although the shipped sentence says “re-analysing will rebuild it”. Re-analyse would be the wrong verb anyway: it’s --clean, which discards the researcher’s stars and tags.

After

A failed read stays .unreadable. A read that worked but won’t parse is .damaged, which is error kind, as it already is. .damaged counts as “pipeline free” for Analyse only. The run sets the file aside (pipeline-manifest.json.damaged-<stamp>) and runs every stage again. Nothing new is drawn: the pill, the glyph, the popover, Show Log and the menu item all ship today. The only change is the one sentence.

Not built (owner, 22 Oct): a last-good copy of the manifest. It’s parked until after V1. R111 came from the 19 Oct audit’s probe, a damaged manifest has never been seen in the wild, and since 04d30698 the write fsyncs before it replaces the file.

1b · The popover sentence: three candidates

Same popover, same width (360 pt) and type sizes. Only the sentence changes, in all 21 locales.

option A — minimal edit (recommended) owner-written · new copy
Project file damaged

This project’s index file couldn’t be read — analysing will rebuild it, running every step again.

/Users/sam/Research/Spring usability study

option B — names the quote risk owner-written · new copy
Project file damaged

This project’s index file couldn’t be read. Analysing rebuilds it by running every step again; stars and tags on quotes that change may be lost.

/Users/sam/Research/Spring usability study

option C — keep today’s sentence (shipped) · menu offers Analyse, sentence says re-analysing
Project file damaged

This project’s index file couldn’t be read — re-analysing will rebuild it.

/Users/sam/Research/Spring usability study

Commentary

A keeps today’s shape (an em-dash clause, as in its siblings in the diagnostic.unreachable.* family) and its first half. It changes the verb to the one on the menu and says what the rebuild costs.

B is honest about a second cost: re-extracted quotes won’t come out identical, so curation on those that change can be orphaned. It runs to two clauses and adds a loss warning without a confirm. The house rule is to confirm only for a real loss, and this one is uncertain.

C needs no translation work, but its verb disagrees with the menu item. That mismatch is the bug this decision exists to remove.

2 · Mac: projects.json is damaged (#19)

At launch, after the file has been set aside. Shown once.

before — today: an empty Welcome screen, no word
Projects

Welcome to Bristlenose

Drag a folder of interview recordings into the sidebar, or click + New Project to start.

after — one sheet alert on the main window, OK dismisses it title + body: approved 22 Oct · new copy
Projects

Welcome to Bristlenose

Drag a folder of interview recordings into the sidebar, or click + New Project to start.

Your project list couldn’t be read

It was set aside and a new list started. Your projects are untouched in their folders, with their reports and edits — drag each folder into the sidebar to add it back.

Commentary

Mechanism: a stock NSAlert (.warning), presented as a sheet on the first window once launch has settled. It isn’t app-modal, because the message is about this window’s empty sidebar. It shows once, after ProjectIndex.load() has moved the file to projects.json.damaged-<stamp>, and never again unless another file is damaged. There is no Show in Finder: the copy sits in the app’s container, where a researcher can do nothing with it.

Remedy 1 (do something): drag the folders back. The research is safe, because each project’s database and edits live in its own folder (bristlenose-output/.bristlenose/bristlenose.db, matched by the study marker). What is lost is the sidebar arrangement: groups, order, icons and agent access.

Edge case, accepted: if the set-aside itself fails (saveRefused), the same alert shows, but projects added back last only until the next launch. That launch tries the set-aside again.

3 · CLI: people.yaml and the manifest (#17, #18)

English by design (the terminal is the operator surface). Glyphs and colours come from ui_kinds.py.

before — run, people.yaml with a typo: every paid stage done, then a traceback
✓ Extracted quotes             1m 12s
✓ Grouped themes                  41s
Traceback (most recent call last):
  File ".../bristlenose/pipeline.py", line 3359, in run
    existing_people = load_people_file(output_dir)
  …
yaml.scanner.ScannerError: while scanning a quoted scalar
  in "<unicode string>", line 4, column 18
after — run completes; the file is left exactly as the researcher wrote it approved 22 Oct
✓ Extracted quotes             1m 12s
✓ Grouped themes                  41s
⚠ people.yaml couldn’t be read (line 4, column 18), so it was left as it is, and names you added aren’t in this report. Fix that line, then run again.
✓ Rendered report                  3s
before — run, damaged manifest
$ bristlenose run interviews/
Resuming from previous run...
✓ Transcribed 6 sessions      14m 02s
  … every stage runs again, LLM calls paid again
after — run, damaged manifest new copy · CLI only
$ bristlenose run interviews/
⚠ The pipeline manifest couldn’t be read, so it was set aside as pipeline-manifest.json.damaged-2026-10-22T21-04-05. Every stage runs again, including the LLM stages.
✓ Transcribed 6 sessions      14m 02s
before — status, damaged manifest (says there is none)
$ bristlenose status interviews/
No pipeline manifest in interviews/bristlenose-output.
Run bristlenose run interviews/ to start.
after — status, damaged manifest (read-only: moves nothing) new copy · CLI only
$ bristlenose status interviews/
⚠ The pipeline manifest in interviews/bristlenose-output couldn’t be read, so Bristlenose can’t tell which stages are done.
Run bristlenose run interviews/ to rebuild it — every stage runs again.
Commentary

people.yaml is the researcher’s file (“edit them freely”, says its header), so a typo is theirs to fix, and the fix is one line in a file left exactly where they made it. The run gets the same tolerance in analyze, render and coverage. With no decision needed, write_people_file becomes atomic (tmp + fsync + replace), so Bristlenose can no longer truncate the file itself.

“Pipeline manifest” stays CLI vocabulary. status already uses it, and a CLI researcher can find the file. The Mac never says it: there it is the shipped “Project file damaged”.

String plan

WhereKeyEnglishStatus
Mac row + popover headerdesktop.chrome.pipeline.unreachable.damagedProject file damagedshipped, unchanged
Mac popover sentencedesktop.pipeline.diagnostic.unreachable.damagedoption A / B / C aboveowner to choose · 21 locales
Mac menudesktop.menu.project.analyseAnalyseshipped, newly offered here
Mac alert titledesktop.projectList.damagedTitle (new)Your project list couldn’t be readapproved 22 Oct · 21 locales
Mac alert bodydesktop.projectList.damagedBody (new)It was set aside and a new list started. Your projects are untouched in their folders, with their reports and edits — drag each folder into the sidebar to add it back.approved 22 Oct · 21 locales
Mac alert buttonbuttons.ok (common)OKshipped
CLI run— (English)people.yaml couldn’t be read (line N, column M), so it was left as it is, and names you added aren’t in this report. Fix that line, then run again.approved 22 Oct
CLI run— (English)The pipeline manifest couldn’t be read, so it was set aside as … Every stage runs again, including the LLM stages.drafted in brief
CLI status— (English)The pipeline manifest in X couldn’t be read, so Bristlenose can’t tell which stages are done. / Run … to rebuild it — every stage runs again.drafted in brief