spec-kitty / spec-kitty/spec-kitty
Bug: `charter status` reports STALE from a retired comparison, and every recovery it names either refuses or no-ops
- Dominant language
- Python
- Stars
- 1.6k
- Forks
- 165
- Avg merge
- 13h 53m
- Merged PRs (30d)
- 336
Description
# Bug: `charter status` reports STALE from a retired comparison, and every recovery it names either refuses or no-ops
## Summary
A project whose `charter.md` has been hand-edited reports `Status: STALE (modified since last sync)`
permanently, and none of the three commands involved can clear it. `charter status` directs the
operator to `charter sync`; `charter sync` refuses and directs them to `charter generate` or
`charter synthesize`; `charter generate` reports "Charter generated and synced" but writes only
`charter.yaml`, leaving the condition unchanged. The remaining suggestion, `charter synthesize`, is a
substantially heavier operation than the situation calls for. The result is a project that cannot
return to a clean charter state, so the STALE signal stops carrying information — a project can sit
STALE for weeks without anything being wrong.
## Reproduction
### Prerequisites
- `spec-kitty-cli 3.2.6` (release tag `v3.2.6`, SHA `5a746e768`)
- A project with a charter, where `charter.md` has been edited by hand — which is the supported way
to change charter prose, since `charter generate` does not rewrite `charter.md`
### Steps
```bash
$ spec-kitty charter status
Status: STALE (modified since last sync)
Expected hash: sha256:468008ea…
Current hash: sha256:7e1f0bf3…
Run: spec-kitty charter sync
$ spec-kitty charter sync
Charter was not synced — charter.md is stale and this command did not repair it.
Run 'spec-kitty charter generate' or 'spec-kitty charter synthesize' to repair it.
$ spec-kitty charter generate
Charter generated and synced
Files written:
✓ charter.yaml
$ spec-kitty charter status
Status: STALE (modified since last sync)
```
### Expected
Either a command clears the condition, or `status` does not recommend one that cannot.
### Actual
The loop closes on itself. `generate` additionally reports "generated and **synced**" while the sync
condition still holds, which reads as success.
## Root Cause
The STALE verdict is computed from a comparison the codebase describes as retired.
`charter_runtime/freshness/computer.py`, in `_compute_charter_source`:
> Landmine 2 (data-model.md): `charter.yaml` — not `charter.md` — is the authoritative, resolving
> charter source post-inversion. The historical `charter.md`-SHA-vs-`metadata.yaml::charter_hash`
> comparison is **retired outright** (module docstring); there is no self-referential hash to compute
> here. This sub-state can therefore only ever be `missing`, `invalid`, or `fresh` — **never
> `stale`**.
Yet `cli/commands/charter/status.py:84-86` still renders `Status: STALE (modified since last sync)`
from a `charter_sync` payload whose `status` is `"stale"`, and `charter status` prints an expected
and current hash pair — the very comparison described as retired.
`charter sync` cannot resolve it because it no longer syncs anything. Its own module docstring:
> Since the IC-04 triad retirement, `charter.sync.sync()` is a pure staleness reporter — `synced` is
> always `False` and `files_written` is empty.
So `sync` is a reporter whose name implies an action it can no longer take, and `status` recommends
it as the remedy. The same docstring records that this surface was already corrected once, under
#3045, for reporting success when nothing had been synced — the present behaviour is the same class
of mismatch one layer up: the human surface now fails loudly, but names a recovery that cannot work.
## Suggested Fix
The narrow fix is to stop reporting a verdict from a retired comparison: if
`charter.md`-vs-`charter_hash` is no longer authoritative, `charter status` should not surface it as
STALE, nor print the hash pair, and should not direct the operator to `charter sync`.
If the drift question still matters — and it plausibly does, since `charter.md` remains the file
humans edit — then the honest form is a distinct signal that says which artifact drifted from which,
resolved against `charter.yaml` as the authoritative source rather than against a retired hash.
Separately, `charter generate` should not print "generated and **synced**" when the sync condition it
names still holds afterwards. That single word is what makes the loop hard to diagnose: an operator
reasonably reads it as the repair having worked.
## Workaround Applied
None available. The charter's *content* can be corrected — editing `charter.md` and
`interview/answers.yaml` together, then running `charter generate` to refresh `charter.yaml`, leaves
every reader and agent seeing the right text. Only the STALE flag persists. Re-recording
`metadata.yaml::charter_hash` by hand would clear the display, but that is writing a value the tool
owns, and the comparison it feeds is retired in any case.
## Environment
- `spec-kitty-cli 3.2.6` (release tag `v3.2.6`, SHA `5a746e768`)
- Re-verified end-to-end 2026-09-08 on spec-kitty-cli 3.2.6 (Linux, Python 3.14): `charter status` →
STALE; `charter sync` → refuses and points to `generate`/`synthesize`; `charter generate` → prints
"Charter generated and synced" writing only `charter.yaml`; `charter status` → still STALE.
Originally observed on macOS (Darwin 25.6.0, Python 3.12).
---
**Reported by**: Kent Gale. Drafted with Claude Code (2026-09-05); re-verified on spec-kitty-cli 3.2.6 and filed with Claude Code (2026-09-08).
**Submission approved by**: Kent Gale (copy, 2026-09-05).
Contributor guide
Research direction
Start with charter_runtime/freshness/computer.py and cli/commands/charter/status.py, then trace the charter sync and generate command entry points. Reproduce the status, sync, and generate sequence from the issue, and compare the displayed verdict, hashes, recovery guidance, and success message with the authoritative charter.yaml model. Done means the retired comparison is no longer presented as a repairable STALE condition and the command messaging is consistent.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100