Skip to content

Scribe archival can silently delete history: Safety Rules are prompt directives, not an enforced gate (2nd occurrence) #1836

Closed

Description

@bradygaster

Summary

Scribe's Archival Safety Rules (A–E) live only in the spawn prompt. They are declared, not enforced — nothing observes whether they were followed. As a result the same data-loss failure has now occurred twice, one day apart, with a repair PR in between that did not prevent the recurrence.

This is the same structural class as #1824/#1812/#1801/#1822/#1825/#1827: a gate that cannot observe its own failure is not a gate.

Evidence — measured, this session

Occurrence 1 (2026-08-19) — repaired by #1782. Scribe reported "no archival required" while having archived 25 decision records + 3 history entries to new, untracked files under .squad/. Caught manually before commit (752 deletions vs 265 insertions).

Occurrence 2 (2026-08-20) — never caught until today. A Scribe run trimmed .squad/agents/eecom/history.md from ~15,119 → ~5,249 chars and wrote this provenance line:

Full pre-summary history archived at .squad/agents/eecom/history-archive-2026-08-20T11-59-44-0700.md

That file does not exist and was never committed:

$ git log --all --oneline -- '*history-archive-2026-08-20*'
(empty)

The pointer has been dangling for two days. git status read clean the entire time.

Occurrence 2 compounded (today). A fresh Scribe run reported History summarization: SKIPPED — no moves performed, then rewrote the same file (67 added / 50 removed) and stripped the dangling pointer itself — erasing the only on-disk evidence that content had gone missing. No work content was lost (verified by heading-level comparison against HEAD), but the provenance trail was destroyed.

Same run also reported decisions.md as 31 + 3 = 34 total. Measured actual: 48 → 56. The count was asserted, not counted (Rule C).

Root cause

Two independent contributors:

  1. squad consult writes .squad/ to the shared info/exclude, silently hiding state in main and all sibling worktrees #1826 — .git/info/exclude hides untracked files only. Tracked .squad/ files commit normally; new .squad/ files are silently unstageable while git status reads clean. An archive write appears to succeed and then evaporates. Requires git add -f.
  2. No enforcement. Rules A–E instruct Scribe to verify the destination is tracked (git ls-files --error-unmatch) and to append-verify-then-delete. Nothing checks that it did. A run that skips verification and a run that performs it produce identical-looking reports — which is precisely the defect class this repo has spent the week eliminating elsewhere.

Rule D already says "never report a gate outcome you did not measure." Occurrence 2 violated Rule D while Rule D was in the prompt. That is the proof that prose is not the fix.

Proposed fix (structural, not prose)

A check whose output survives the run and can be asserted — following the pattern at workflows/squad.md:287:

  1. Dangling-pointer scan. Walk .squad/**/*.md for archived at <path> / Prior archive at <path> and assert each target both exists on disk and returns exit 0 from git ls-files --error-unmatch. Any dangling pointer = fail loud, naming the file.
  2. Wire it into squad doctor, alongside the checkWorktreeEol check added in #1790's *.mjs text eol=lf does not repair existing working trees — 3 suites stay dead after merge #1793 — same shape, same reporting surface.
  3. Prove it can fail. Per the standing bar: a fixture with a deliberately dangling pointer must turn the check red, and the assertion must name the offending file, not merely return non-zero. (#1790's *.mjs text eol=lf does not repair existing working trees — 3 suites stay dead after merge #1793's review established that a status-only assertion passes a truncating parser that reports a filename which doesn't exist.)

Non-goal: a CI gate. CI has a fresh checkout and could never observe the working-tree condition — the permanently-green anti-pattern.

Recovery status

Content is recoverable — commit 3dace32e holds the pre-summary ~15,119-char version. Repair is in progress this session: materialize the archive at the path the pointer claims, stage with git add -f, prove tracked, restore the stripped provenance lines, and enumerate any other dangling pointers across the other 15 agents.

Acceptance criteria

  • squad doctor fails on a dangling archive pointer and names the file
  • Check proven capable of failing via fixture (mutation evidence, not assertion)
  • Full sweep of all agent histories + decisions.md for existing dangling pointers, each enumerated with recoverability
  • Rules A–E remain in the prompt but are no longer the only line of defense

Activity

  1. bradygaster commented on Aug 23, 2026

    @bradygaster
    CollaboratorAuthor

    Verified scope correction: three agents, not one — and the trim commit is named "repair archives"

    Measured while repairing. All numbers below are reproducible with the commands shown.

    1. The incident hit three agents, not one

    A single Scribe run at 2026-08-20T11:59:44-0700 wrote this provenance line into three agent histories:

    Full pre-summary history archived at `.squad/agents/{agent}/history-archive-2026-08-20T11-59-44-0700.md`.

    That archive was never created for eecom, pao, or procedures. Three dangling pointers, git status clean, undetected for two days.

    2. The commit that destroyed the content is titled "repair archives"

    commit c508d8660fdf7a1d69568ccc5c915e67aa7d0014   2026-08-20 13:41:28
            "chore(squad): record gh-aw triage session state and repair archives"
    
    pao         PRE  012ea605 = 13,605 B  ->  POST  db0d4d95 = 3,636 B
    procedures  PRE  1298528a = 13,653 B  ->  POST  67b1f6b3 = 3,997 B
    

    Reproduce:

    git rev-parse c508d866^:.squad/agents/pao/history.md   # 012ea605...
    git cat-file -s 012ea605                                # 13605
    

    This is the defect wearing its own headline. A commit message is a declaration; nothing enforced it.

    3. Still-missing content, measured by line comparison rather than size

    agent pre-trim today non-blank pre-trim lines absent today
    pao 120 lines / 13,605 B 114 lines / 10,634 B 44
    procedures 119 lines / 13,653 B 135 lines / 15,370 B 35
    eecom 129 lines / 15,063 B — recovered, blob 771d9e0d

    procedures is 1,717 bytes larger today and still missing 35 lines. Later sessions appended while the trimmed material stayed gone. Any size- or growth-based heuristic reports this file as healthy. Only content comparison detects it.

    Both pao and procedures also lost ## Notable retained signals; pao additionally lost its entire 2026-08-19 provenance block, so the prior archive pointer on line 5 lost its own explanation.

    4. A count-based verification passed over a corrupted file

    During the eecom repair, the recovered archive was written by PowerShell 5.1 Out-File -Encoding utf8, which prepends a UTF-8 BOM (EF BB BF) and dropped the trailing 0A:

    source  771d9e0d : 15,063 B   first=[23 20 45] "# E"   last=[... 2E 0A]
    written 034768c7 : 15,065 B   first=[EF BB BF] BOM     last=[... 2E]  (no EOL)
    

    Character count, line count, first-line check, and heading-containment all passed on the corrupted file — .NET ReadAllText silently strips BOMs. Only blob SHA equality could observe the failure.

    This belongs in the fix design: git ls-files -s <path> compared against the source blob SHA is the only integrity check here that cannot be fooled. Recommend the dangling-pointer scan assert existence + tracked + blob equality, not counts.

    5. Why prose remediation failed twice

    #1782 fixed the identical failure on 2026-08-19 by repairing content and adding Archival Safety Rules A-E to Scribe's prompt. Recurrence came one day later, with Rule D ("never report a gate outcome you did not measure") violated three separate ways while sitting in the prompt.

    During this repair, the same agent reported PAO & Procedures: UNRECOVERABLE - pre-summary versions not found in git history (all commits < 10KB). pao has 41 commits at 13,605 B; procedures has ~30 at 13,653 B. The verdict and its supporting measurement were both false, and the content was recovered from blobs the sweep claimed did not exist.

    Prose in a prompt is a declaration. It is not an enforcement. Two independent failures now support that, which is the argument for the squad doctor scan over a third round of prompt hardening.

  2. added a commit that references this issue on Oct 26, 2026
    9dea3a8
  3. added a commit that references this issue on Oct 26, 2026
    a0de118
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions