Conversation
syscall.Errno implements Timeout() and Temporary(), so every errno satisfies net.Error on its own. errors.As walks past *fs.PathError, which has Timeout() but no Temporary(), and lands on the errno beneath, so pressureFailure read an ENOENT from a vanished session file as a network failure. The worker then re-marked the file and slept for the pressure backoff. Nothing about a deleted file changes on a retry, so the dirty set could never drain. One macOS host spent eleven days cycling 938 transcripts from removed worktrees on a 30-second loop, writing ~2.3MB a day of identical errors into an unrotated log. Classify the transport by the net package type that a real transport failure always carries ahead of its errno, and name the exhaustion errnos that the interface match used to cover by accident, so EMFILE and friends keep backing off deliberately rather than incidentally. The old test passed a bare errors.New for the filesystem case, which has no errno underneath and so never exercised the path that failed. Take the error from a real os.Lstat as well as a synthetic *fs.PathError, and assert the worker leaves the dirty set empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
akari watchcan enter a retry loop it never leaves.A session file deleted between discovery and upload — routine when a git worktree is removed, or when an agent harness cleans up subagent transcripts — fails with
ENOENT. That is classified as resource pressure, so the worker re-marks the file into the dirty set and pauses forPressureBackoff(30s by default). Nothing about the next attempt can change the outcome, so the set never drains and the watcher spends the rest of its life cycling deleted files.On one macOS host this ran for eleven days over 938 deleted transcripts, writing ~2.3 MB/day of identical errors into an unrotated log. The same loop was present on all four clients in that fleet, including two Linux ones.
Mechanism
pressureFailureended with an interface match:syscall.Errnoimplements bothTimeout()andTemporary(), so it satisfiesnet.Errorentirely on its own.errors.Aswalks straight past*fs.PathError— which hasTimeout()but noTemporary()— and matches the errno underneath. A missing file was therefore as firm a "network failure" as a dropped connection:Fix
Identify the transport by the net-package type a genuine transport failure always carries ahead of its errno, and reject a match that lands on a bare
syscall.Errno.The exhaustion errnos that the interface match used to cover by accident —
EAGAIN,EMFILE,ENFILE,ENOMEM— are now named explicitly. Without that list this change would have silently stopped descriptor exhaustion from backing off, trading one bug for another.Tests
The existing filesystem case passed
errors.New("read session header"), which has no errno beneath it and so never exercised the path that fails. It is replaced with real error shapes:*fs.PathError{Err: syscall.ENOENT}, matching whatresolveactually wrapsos.Lstat, so a future change to the standard library's method set cannot quietly reintroduce this*url.Errorwrapping*net.OpError, the shape the upload client really returnsAll of them fail against the current classifier and pass with this change.
Note
An earlier revision of this description claimed
internal/server/parse.TestGoldenProjectionfails on
main. That was wrong and I have corrected it — CI is green on this PR.What I actually hit is worth flagging on its own, though: that test fails against an
unmodified
mainwhen built with Go 1.27.1, while CI passes on the 1.26.6 fromgo.mod. The sole difference is oneBodyfield carrying a PNG blob — the golden escapesthe invalid UTF-8 as
\ufffd, whereas newerencoding/jsonemits the replacementcharacter literally. Contributors on a newer toolchain will trip over it. Happy to open a
separate issue if that is useful; it is unrelated to this change.
🤖 Generated with Claude Code