Repository navigation
Description
What happened
On dev@b5b7dead1, a chat whose project folder lies inside a directory that an enclosing Git repository ignores cannot be saved. While it runs, the window says:
calls are not being sealed, so rewind cannot reach them: screen for secrets: cannot read which folders Git ignores here: exit status 128
On quit:
sync: closing a chat: cannot read which folders Git ignores here: exit status 128
codeaf could not save this chat; unsaved turns are kept in <home>/v3/projects/<project>/<id>/.cell/transcript.jsonl and return on the next start if this computer still holds the chat.
The process exits 1, and the chat never reaches the other computer. The same happens with GIT_CEILING_DIRECTORIES set to the project folder itself, which Git does not treat as a fence. Setting the ceiling to an ancestor above the enclosing repository avoids it.
The immediate cause is Git itself. From inside an ignored directory, this plumbing call exits 128 (seen with Git 2.50.1):
$ git --no-optional-locks -C <repo>/w/p ls-files --others --ignored --exclude-standard --directory -z
fatal: git ls-files: internal error - directory entry not superset of prefix
codeaf treats that unexpected failure as "cannot read which folders Git ignores here", deliberately, so as not to capture ignored private files. The chat therefore cannot be sealed at all.
Replication
Deterministic (no model). The Git step alone:
mkdir -p /tmp/ig/g/w/p && git init -q /tmp/ig/g
printf 'w/\nw/p/secret.txt\n' > /tmp/ig/g/.gitignore
printf 'CONFIDENTIAL-MARKER\n' > /tmp/ig/g/w/p/secret.txt
git --no-optional-locks -C /tmp/ig/g/w/p ls-files --others --ignored --exclude-standard --directory -z; echo "exit $?"Through codeaf, with the keyless stub (go build -o /tmp/ig/stub ./test/remote/stub, run with -addr 127.0.0.1:18842, OPENROUTER_API_KEY=stub CODEAF_BASE_URL=http://127.0.0.1:18842/api/v1) and a Go relay (bin/codeaf relay --listen 127.0.0.1:18843 --store /tmp/ig/store, CODEAF_SYNC_URL=http://127.0.0.1:18843):
- Pair two fresh homes. Only the approving home opens codeaf first; the joining home must never have run it (see A computer that opened codeaf once cannot join another: 'this computer has local work' with no chats #2538).
- On the first home, run
bin/codeaf chat --no-host --model stub/scripted, then type/project /tmp/ig/g/w/pandPROBE-ECHO ignored-workspace-words. - Quit. The sealing refusal and the "could not save this chat" receipt appear, the exit status is 1, and the second home never receives the chat.
Where
ignoredDirectories in internal/cellstore/ignored.go: the ls-files --others --ignored --exclude-standard --directory call, and its handling of an unexpected Git failure as ignoredReadFailure. Its callers in the secret screen and seal path then refuse to seal.
The fix
A project folder inside an ignored directory is still a chat that saves and moves. Read Git's ignore answer in a form Git can give from that position: for example, run the listing from the repository's top level with the project folder as a pathspec, or first ask git check-ignore about the folder itself. Apply the result: files Git ignores stay out of saved history, including an ignored secret.txt. The chat's own words always save. A genuinely unreadable answer still refuses rather than capturing ignored files.
Findings from a first attempt
A codeaf-only workaround was tried in #2540 and taken back out. Asking Git about the folder first, then keeping an ignored project's top-level entries out by listing them as exclusions, made the save work in the simple case. A review then found four problems:
- The project's own
.furrowpolicyentered saved history unscreened. The engine's policy parser forbids excluding that file. - The chat was still lost when that
.furrowpolicyturns ongitignore. The engine unions both policies, and for an ignored root itstracked_childrenreturns nothing. That drops the composed.cellentry; this is A wholly Git-ignored workspace drops the composed .cell entry from the capture furrow#28. - One rule per top-level entry breaks the engine's limit of 1,024 rules and 64 KiB per policy.
- Recognising "no repository" from Git's English message breaks on non-English Git, and the extra Git calls added several processes to every save.
So the fix needs the engine to keep the composed conversation when Git ignores the whole folder (furrow#28). After that, codeaf's part is to ask Git about the folder in a form Git can answer, and to keep the refusal for a genuine Git failure.
Acceptance
- e2e: the replication above quits with exit 0, the second home continues the chat with
STUB-REPLY: PROBE-ECHO ignored-workspace-words, andCONFIDENTIAL-MARKERappears nowhere in the second home. - e2e: controls: an ancestor ceiling, and an ordinary project that is its own repository, behave as today.
- Unit: the ignore reading for a folder inside an ignored directory returns the ignored paths without error. A real Git failure still yields
cannot read which folders Git ignores here.
Activity
Take the ignored-project fix out of this pull request
A computer that only opened codeaf can pair, and the local relay stop…