Repository navigation
Replies: 21 comments
The remote panel was empty on Blomp, and the tab had lost its logoPost-4.1.8 follow-up, fixed on Two defects reported against v4.1.8, one raised by CodeRabbit on the fix for the first, and one internal cleanup that rode with them.
The listing defect predates v4.1.8. It has been there since v3.1.6 and surfaced now because v4.1.8 put Blomp back in front of people. The v4.1.8 notes were accurate about what they claimed, the cleartext guard was restored and the connection does succeed, but a user who read them and opened the app saw an empty panel, so the correction belongs in the next patch rather than a later release. Worth recording, because it is the second time in this cycle: the session tab drew the generic icon while the logo, the asset and the mapping were all present and correct. Nothing was missing. One Verified green: cargo fmt, clippy at |
WebDAV survives a Digest nonce rotation instead of reporting the session expiredPost-4.1.8 follow-up, fixed on An Apache The blocker was not the missing retry. It was that the retry could not be decided:
The repair is pool-wide, which is what makes it worth doing at all: the challenge lives behind a shared Tests. Seven new, two existing corrected. The data-path tests run against a real socket: there is no HTTP mock in
Two existing tests passed challenges without Not verified, and stated rather than assumed: live validation against a Digest server that rotates its nonce has not been performed, and the chunked-PUT disk replay is not exercised end to end, since that needs both a Digest server and a real multipart upload. Verified green: |
The strings that shipped as raw English inside translated locales are gone, and a gate keeps them outPost-4.1.8 follow-up, fixed on About 55 keys read as English in 43 to 46 locales at once. The obvious test for the second is useless. "Value identical to English" fires on 15992 pairs across 1093 keys, and almost all of them are correct: FTP, AeroCloud, RAG, us-east-1, ARNs. Any gate built on it prints sixteen thousand lines that nobody reads, which is exactly why the class survived.
The baseline is evidence, not opinion. Several entries were decided by experiment rather than by reading the string.
A near miss worth recording, because it is reproducible and it is a property of the batch-translation method rather than of this change. Each translator agent receives the union of the locales in its group, so it translates every key for every locale in that union, including pairs that were already correct. Forty-one good translations were nearly overwritten, twenty of them in Diacritics surfaced by translation, not introduced by it. Three stripped-diacritic strings appeared during the work ( Tests: 8 behavioural, over a fixture copy of the locales. A gate that has never been seen to fail is not a gate. Clean tree passing, a new untranslated string breaking the build, a Verified green: |
Every WebDAV request path re-negotiates a rotated Digest nonce, and the live validation is donePost-4.1.8 follow-up, fixed on Item 6 built its map by grepping for
Live validationApache 2.4.58 with
Where the boundary isSweeping the nonce lifetime against injected one-way delay produced a rule rather than a set of cases:
The nonce is minted when the server emits the 401 and must still be alive when the replay arrives, which is one round trip later. There is no margin to recover: the credential expires in flight.
The threshold moves with The maximum number of 401s on any single path separates the regimes with no ambiguity: 1 means healthy, 2 means the configuration is unsatisfiable because the replay went out and died too. The broken regime needs Known limit, recorded rather than hiddenThe nonce snapshot is read just before the request is built, while Verified green: |
Every published figure now comes from the code, and a gate fails when it driftsPost-4.1.8 follow-up, fixed on Three different numbers for the same noun were published at once and none of them errored. The README said 25+ native integrations and 45+ presets.
The figures, and which noun each one answers
A preset is a form that arrives already filled in. That is the split the project has always drawn, specific against generic, and it is now derived from the data rather than from an id prefix: the two generic forms carry an empty MCP is 77, the 39 primary tools plus the 38 aliases, which is what an agent connecting to the server actually sees. Not to be confused with the 39 AeroAgent tools, which are a different surface inside the app: pages saying "AI assistant with 39 tools" are about that and are correct as they stand. Release notes were left alone. The Verified green: provider inventory current, |
pCloud's trash button is removed, because pCloud will never honour itPost-4.1.8 follow-up, fixed on This is the last "Known issues found after release" row, carried on three trackers (#1414 known issue #4, then #1415 known issue #1, then here). It survived twice because someone read the code, found the trash path correct, and left the row open pending a live test. The code was correct. That is exactly why reading it again was never going to close it, so this time it was run against a real pCloud account. pCloud refuses its trash endpoints to OAuth access tokens. The error message was telling the truth all along, and the previously recorded cause on this tracker (a stale token, plus an endpoint that supposedly rejects Bearer header auth) was wrong on both counts.
Wire evidence, one connected session, one token, seconds apart:
For corroboration outside AeroFTP: rclone does not implement CleanUp for pCloud at all, while it does implement it for Google Drive. The pCloud web and desktop clients reach the trash because they authenticate with a session login rather than a third-party OAuth token. Verified green: typecheck clean, i18n validation clean (46 locales, 0 placeholders), vitest 801 passed across 93 files, and CI green on all four platforms (Linux, Windows, macOS aarch64, macOS x64) before merge. No Rust changed. |
DigitalOcean Spaces is no longer hidden behind an unstable flagPost-4.1.8 follow-up, fixed on This supersedes the DigitalOcean half of queued row 15. That row recorded why the preset stayed gated; the owner's call is that the reason never justified the gate in the first place, so the gate is lifted instead.
Verified green: provider inventory current, typecheck clean, i18n validation clean, vitest 801 passed across 93 files, and CI green on all four platforms (Linux, Windows, macOS aarch64, macOS x64) before merge. |
A user can restyle the app chrome, and the request as written was declined on purposePost-4.1.8 follow-up, fixed on The request was Obsidian-style CSS snippets, raised when the preview scrollbar was widened in v4.1.1. What shipped is narrower, and the reason is the substance rather than an excuse. Obsidian can offer a snippets folder because its DOM is a public contract:
What the live test found, which is why it is not optionalEvery gate was green and the feature did nothing. The fs plugin cannot reach the data root. Widening the scope was considered and rejected rather than skipped: One conflation is fixed because it is what hid that bug. The loader caught every failure and returned null, so "forbidden" and "absent" produced the same silent result: a file that was never read looked exactly like a user who had not written one. The startup load ran twice, so four bad values produced eight rejections. React StrictMode double-mounts precisely to surface effects that are not idempotent, so the fix was to make the effect idempotent rather than to silence the signal.
Nine locale defects no gate can seeThe Greek string carried the English Six further findings are recorded rather than guessed at. They need a native reader, and guessing would repeat the mistake they are meant to fix. Live test: 10 of 10Driven through the WebKit inspector against the dev build. Baseline, override applied, survives five theme switches, Verified green: Two labels are left as owner decisions rather than oversights: "Reset to defaults" clears the document but leaves the file, so the reset lasts until the next start, and |
A yanked crate turned
|
| Area | Description | Status | Ref |
|---|---|---|---|
| Lock | chacha20 0.10.1 replaced by 0.10.2. The lock carries two majors and only one moved: 0.9.1 comes from chacha20poly1305, which requires ^0.9 and is untouched; the yanked 0.10.1 came in through rand and ssh-cipher |
🟢 fixed | b5dea635 |
| Method | Edited by hand rather than resolved. cargo update -p chacha20@0.10.1 --precise 0.10.2 also re-resolved two unrelated dependencies, downgrading syn inside data-encoding-macro-internal and getrandom inside tempfile. Both resolutions are valid, neither belongs to this fix, and a dependency change should be the change it claims to be |
🟢 done | b5dea635 |
The resulting diff is four lines: the version, the checksum, and the two references.
One caveat stated rather than glossed over: this landed minutes before the next change, so the Checks run for b5dea635 alone was cancelled by the concurrency group and the green belongs to e8768e44, which contains both commits. The second commit touches only locale JSON and no Rust, so a passing cargo audit is attributable to this one, but the isolated proof does not exist.
Verified green: cargo metadata --locked exits 0, so the hand-edited lock is coherent and cargo would not rewrite it; cargo check --locked compiles the workspace clean; cargo audit reports no yanked crate where it reported one before. On main, the Rust dependency audit (cargo audit) step is success in run 33172943128, the exact step that was failing.
Two labels that described the wrong thing, and a review pass that refused two of its own findingsPost-4.1.8 follow-up, fixed on Two decisions were left open when the UI tokens panel shipped. Both are closed here, and neither changed behaviour: in each case the label was wrong, not the code.
The automated review pass on the same change was verified finding by finding rather than accepted or dismissed in bulk, and it split four ways.
One defect was caught by hand rather than by any check: the batch rendered Korean without the space after the full stop, following the convention of A green that does not prove what it looks like, recorded because it will be cited again. Verified green: |
The inventory gate was pointed at the wrong thing, and reported OK for itPost-4.1.8 follow-up, fixed on
The proof, by differential rather than assertionA snapshot with one command removed from
Both exit codes read without a pipe. The committed Two review findings, both verified and both applied
TestsSix. One reads Verified green: |
B2 was told the bucket's settings at every connect, and threw them awayPost-4.1.8 follow-up, fixed on B2 caps Both settings were already arriving.
Two limits declared rather than hiddenThe documented measure for the 7000 byte budget is the fully encoded HTTP header line including the header names and CRLFs. This check measures the raw name and counts neither, so it is under-strict by construction rather than by oversight. And because file names are separately capped at 1024 bytes, a legal name charges at most 1062 bytes raw, 3110 encoded, and 3166 once the header overhead the documentation counts is added. None of those reaches 7000. The defensible claim is therefore not that B2 counts the raw form or the encoded one, but that no legal B2 name can reach the standard limit even encoded: refusing against 7000 is safe under either reading of the documentation, the reduced case stays undetermined and is reported rather than blocked, and what shipped today is the diagnosis, not the refusal. Revising the model against the three Backblaze pages that disagree with each other is queued as its own change, rather than rewritten into this one after review. Four review findings, all verified, none applied in the form proposed
A fifth was declined with its reason recorded: measuring the encoded form against the reduced budget would make us stricter than B2 under the reading its own documentation supports on another page. Verified green: |
FTP: one parser, errors that quote the server, and an empty listing that no longer means two thingsPost-4.1.8 follow-up, fixed on The campaign started from a measurement, not from reading code: a probe of a path that does not exist, compared against a genuinely empty directory created in the same session for the purpose, returned identical responses byte for byte.
Verified live against a vsftpd 3.0.5 server over explicit FTPS, on both the CLI and the MCP surface, with a genuinely empty directory measured beside each missing path so the pair is readable rather than a single result that could be read either way:
Known and declared: this laboratory cannot exercise the MLSD path, since no server used in the campaign advertises |
Two FTP and provider corrections landed on
|
| Area | Description | Status | Ref |
|---|---|---|---|
| Jottacloud trash | Rename, restore and permanent delete each failed for a different reason: the rename went to the live mountpoint, the restore read a tombstone as an ordinary file, and the listing accumulated <abspath> text without decoding it, so a folder named with an entity lost the spaces around it and the restore targeted a parent that does not exist |
🟢 fixed | 4099cc2f |
| Profile identity | An imported refresh token is bound to the record it belongs to, and profile_id is stamped from the saved record with options-borne values dropped at one choke point, so the CLI, MCP, agent and AI tool paths enforce the same policy instead of one file reversing another |
🟢 fixed | 4099cc2f |
| Error causes | Filen quota errors keep their REST cause instead of collapsing into "No user info data", so an expired token no longer presents itself as a missing quota | 🟢 fixed | 4099cc2f |
| Recursive descent | Every descent in providers/ now uses the same walkable-directory test. It changes behaviour on one provider and is cosmetic on the rest, and the cosmetic sites are converted anyway so the contract can be checked by a grep rather than by a reader |
🟢 fixed | f6089243 |
What is not covered is stated in each pull request rather than left to be discovered: the Jottacloud change closes part of #397 and not all of it, two of its tests pin their helper and not the call site, and the rclone-obscure collision is declared rather than narrowed, since any 22-character password drawn from [A-Za-z0-9_-] reveals as empty and password-manager alphabets fall inside it.
Verified green: fmt, clippy with warnings denied, 3637 tests passed with 0 failed on the first and 874 on the second, and every check green on both merged heads with the two publishing lanes skipped as they always are on a pull request. Both were reviewed on the head that merged, not on an earlier one.
FTP data channel: what #655 closed, and what it did not
It does not close the reported hang, and the live battery on What this costs is not one stalled download. A Those three cases were each stopped at a cap. The measurement is therefore "no response within that time", not "never", and the caps are given so anyone repeating it knows what was actually observed. The wait is one step earlier than the guard. Opening a data channel in passive mode sends PASV, reads 227, sends RETR, connects the data socket and performs the TLS handshake on it, and only then reads the reply. The 388 bytes sent with nothing received are a TLS ClientHello nobody answers, so the process is stuck inside that handshake and never reaches the read loop the guard protects. The fix holds exactly where it was put, on a path one step away from the one the title suggests. The 55 unread bytes on the control channel are consistent with the server having already refused. A TLS 1.2 AES-GCM record costs 5 bytes of header, 8 of nonce, the payload, and 16 of tag, and a 26-byte payload gives 55: If that reading is right, bounding the opening step does more than fail fast. The server's own reason is already sitting there to be read, so the user can be told The remedy belongs to the shared data-loop primitive and is recorded there: a deadline on the opening step as well as on the loop, and on expiry a look at the control channel before deciding, reusing the non-consuming peek and the bounded reply read that The binary under test was checked for the fix before any of this was concluded, so the measurement is not of an older build. |
Five fixes from one seam: a guard that answers a question next to the one askedPost-4.1.8 follow-up, all five on These landed together because they are one shape rather than five bugs. A guard reads an error message, or a count, or a status, and answers a question ADJACENT to the one it was asked: is anything on the socket, rather than is anything queued; does this error mention time, rather than is the clock wrong; how many files import this crate, rather than how many users are affected. The answer looks like the right kind of thing, so nothing downstream notices.
The one to read if you only read one is What the tests do differently, because it is the part that stays. Each fix carries a test that was RED first and states the defect in its own numbers rather than asserting the new behaviour: "the peak follows the input: 200 MB of file plans 200 MB", "1 of 18 closed-session messages are classified as permanent", "2 of 5 socket error kinds print something no needle matches". Two of them derive their corpus from the source that produces the strings, a shared A live re-measurement of the FTP cases against this tree follows in a separate comment, with the results table. Verified green: |
Live re-measurement on
|
| # | Scenario | Surface | Result | Identified by | Time | Ended by | Tree |
|---|---|---|---|---|---|---|---|
| T1 | List a path that does not exist | CLI | Fixed | Invalid path: [550] naming the path |
2.4s | client | 80f470a8 |
| T2 | List a directory that is genuinely empty | CLI | Fixed | 0 items, which the defect could not produce beside T1 |
2.4s | client | 80f470a8 |
| T3 | List a path that does not exist | MCP | Fixed | Invalid path: [550] naming the path |
1.4s | client | 80f470a8 |
| T4 | List a directory that is genuinely empty | MCP | Fixed | count: 0, entries: [], beside T3 |
0.5s | client | 80f470a8 |
| T5 | Download a file that does not exist | CLI | Fixed | Invalid path: [550] 550 Failed to open file. naming the file |
33.1s | client | 80f470a8 |
| T6 | Download a file that does not exist | MCP | Fixed | same refusal, same wording | 30.2s | client | 80f470a8 |
| T6b | A listing issued straight after T6, same profile | MCP | Fixed | count: 2 with the real entries |
0.4s | client | 80f470a8 |
| T7 | mkdir on a directory that already exists |
CLI | Fixed | Path already exists |
2.2s | client | 80f470a8 |
| T8 | stat on a real file, size hydrated |
CLI | Fixed | 40961, a size nothing else here shares |
2.2s | client | 80f470a8 |
The wait is BOUNDED, not short, and the table says so in the rows themselves. The 33 seconds on T5 are the opening budget being spent, not latency: a user asking for a file that is not there still waits half a minute before being told. "Fixed" on that row must not be read as "immediate". What changed is that the answer arrives at all, and that it names the file and carries the server's own 550.
T6b is the row that changes the severity. Before, a listing issued straight after the stuck download never returned either, because the download held the pooled connection: the whole profile stopped answering, not just the transfer. It now returns in 0.4 seconds with the real entries. The defect was never only "a download that does not finish".
No row ended on a ceiling. The battery's caps were raised above the code's own (120s against the 30s opening budget, 600s against the 300s listing budget) precisely so that a number produced by the harness could not be mistaken for a measurement of the client. The "Ended by" column reads client on all nine.
One risk was measured rather than reasoned about. 18d4a9a1 made a server-closed connection retryable, which could have inflated these times. T5 was run twice, with retries disabled and with the default policy: 33.1 seconds both times and no retries at all. The reason is specific and is recorded in the table rather than left to be inferred: this path ends on a REFUSAL, which is permanent and correctly not retried, while 18d4a9a1 changed the classification of connections the server closes.
Provenance, because a live result is worth nothing if the binary under test is not the tree claimed. The discriminator is a symbol #663 introduced, checked against the previous tree before being trusted: open_with_cap appears 46 times in the binary under test and 0 in the one built from the previous main. The counter-proof is read_watching_control at 69 in both, so a zero on the discriminator would be distinguishable from a method that is not reading symbols at all. An independent count on the source gave 0 against 7 across the same pair: two different instruments, same direction.
Verified green: the five pull requests each passed four platform builds, clippy --all-features --tests at zero, and the Rust library suite at 3651.
Three more of the v4.1.8 retests, from the same passPost-4.1.8 follow-up, fixed on
Two limits, declared rather than left to be met. The table reorder was verified as far as an automated driver can reach: the table is the live view, 113 stable ids are present, the grips are there and a synthetic Rclone's obscuring is AES-256-CTR with no MAC, so nothing in the value distinguishes an obscured empty string from a 22-character URL-safe Base64 password that happens to look like an IV alone. Password-manager alphabets fall inside that set. Refusing empty reveals reopens this issue; treating every IV-shaped decode as empty turns those passwords into an empty key silently. The collision is accepted and named rather than narrowed, and the remedy is a control beside the field saying the value is rclone-obscured, which is a product decision and not in this change. |
Two corrections to things that had already been written downPost-4.1.8 follow-up, both on Neither of these is a new defect found in the code. Both are records that were wrong: a review nobody opened, and a rationale nobody rechecked. They are grouped because the failure is the same one, trusting a written thing without reading it.
The first four were found in a review that had already been posted on the merged head and was never opened. Three of the four were introduced by the change it reviewed. The one that matters guarded the speed tool's overwrite check, whose own comment says it fails closed on a caller-named path because it treats "not there" as permission to write and then delete: a permission refusal read as an absence would have destroyed a real file. The fifth is a rationale that was checked and did not hold. 2.11.1 introduced no check classifying It is all or nothing because the The file external reviewers read also carried a false absolute, unrelated to the pin. It is corrected and scoped to the platform on which it holds, and stating the scope makes the ignore stronger: outside Linux the application never loads an external URL in production at all, so the entry point the advisory describes is absent there. Verified green: |
Five more from the FTP quality campaign, and two of them are a gap in this trackerPost-4.1.8 follow-up, all five on Rows 38 to 42 above. Two of them are backfill rather than news: #655 and #660 landed on 30 August and were never logged here, which is worth saying plainly, because this table is supposed to be the running record of every push and for two days it was not. They are entered now in main's order rather than at the end of the campaign, so the sequence still reads correctly.
Three things found by the work rather than by the planA remedy that would have been worse than the defect. The obvious fix for row 38 is to end the wait as soon as the server speaks. The completion reply arrives on that same channel, so that version aborts transfers that are working and truncates files. Reading the first digit without consuming it is what separates the two. A green that proved nothing, twice, in different shapes. The loopback fixture cannot reproduce row 38 at all: there the data socket closes first and the wait ends by itself, same server software, opposite outcome. And in row 42 An invariant a diff cannot show. In row 42 the strongest guarantee is that no hand-written constructor exists for the not-found variant, so it can only come from an VerificationEach of the five merged with its own CI green: |
v4.1.9 is released, and this tracker is closedThe Release That Stopped Believing Its Own Gates, published 2026-09-01 with 9 binaries and 9 Sigstore bundles, one per artifact. 65 rows in the queued table, from 55 commits in the cycle plus the release itself.
Two things this cycle got wrong and corrected in the open, because they are the useful part. Four commits shipped with no tracker row at all, two of them features, so the changelog built from the tracker alone would have released the AeroSync ACL support and the language chip without mentioning either; the rows were backfilled and the check now reports 55 commits and zero uncovered. And the declared scope of the rclone reveal collision was WIDER than the truth, caught by the regression test written to confirm it: it is one in sixteen of such passwords, not all of them. Verified before the tag, on the exact commit and not on the branch: zero orphaned check runs, zero incomplete, zero failed across Linux, Windows and macOS on both architectures, and Continues in the 4.1.10 tracker, with the still-open "Requested / under consideration" rows carried over. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
AeroFTP 4.1.9, Rolling Tracker
Umbrella issue for the next AeroFTP point release (v4.1.9): known issues reported against v4.1.8 and the patches/updates landing on
mainafter the v4.1.8 tag.Before opening a new issue, please check the lists below. If your problem is already tracked here, just add a thumbs-up reaction or a comment with your environment (OS, version, provider) instead of filing a duplicate. We update this issue as bugs are found and fixed.
Found something that is not listed? Open a new issue and we will link it here.
What shipped in 4.1.8
See the v4.1.8 tracker (#575) and the release notes for the full change set, 21 tracked changes over 126 commits. It was meant to be an ordinary point release. Four independent adversarial audits ran over the 111 commits behind it, two independent merges of those reports were reconciled against each other, and what came back changed the shape of the release: ticking read-only on a symbolic link applied the LINK's mode to the file it pointed at, so a convenience link to a private key handed that key to every other account on the machine; a guard that refused a cleartext object store after an encrypted login had been removed for every OpenStack account while the provider it was meant for shipped as stable; kDrive discovery had never once reached the network; and the AUR preflight executed the package file it documents itself as only inspecting. The pre-tag pass then audited those fixes and found three more defects introduced by them, including a panic. On the feature side: Quick Connect discovers buckets and drives, rclone export covers Zoho WorkDrive and encrypted overlays, folder downloads over WebDAV honour the concurrency setting, and the multipart resume store stops growing forever and gains a command to clear a decommissioned server. 47 languages, with 47 strings this release had shipped with their diacritics stripped put back, 28 corrupted Icelandic strings retranslated, and 187 banned dash characters removed with the validator now rejecting them.
Patches & updates queued for 4.1.9
Landed on
mainafter the v4.1.8 tag; shipping in v4.1.9.3b831d4ahttp://127.0.0.1with the port handled automatically, which is incomplete now that the app displays the exact callback URI. Deliberately NOT touched: the "22 protocols" claim repeated on eight pages, and the tool and command counts, both of which are decisions rather than corrections and are queued separately.., which the Swift normalizer treated as a literal directory name rather than the container root, so every request asked the server for a directory called "." that does not exist. The normalizer resolves.and..now instead of only trimming slashes, so the fix covers all nine call sites that pass through it, not just the listing: upload, mkdir, rmdir, delete and the two renames. A repeated slash inside a name is preserved, since a Swift object name is an opaque key anda//bis a different object froma/b(raised by CodeRabbit on the pull request). Present since v3.1.6, surfaced now because v4.1.8 put Blomp back in front of people. Reported by axpnet against v4.1.8.fa0325d3PROVIDER_LOGOSentry and the drawing code were all correct: they were unreachable behind a hand-written allowlist of 28 names that nobody had extended. The rule is inverted now, declaring the two protocols that have no logo (ftp,ftps) and treating everything else as a provider, so adding a provider no longer depends on remembering this list. A test reads theProviderTypeunion from source and fails when the two drift apart. Besides Swift,googlephotos,aerocloudandpeerwere latent.fa0325d3profile_loader::normalize_profile_option_keywas the canonical table, whileagent_session::camel_to_snakeandai_tools::load_provider_extra_optionseach carried their own and knew none of its aliases. Harmless while the protocols those paths reach read no aliased key, but it was where the next connection option would have died silently. Closes item 16 of "Requested / under consideration". Harmless at the time because the protocols those paths connect read no aliased key, but it is where the next connection option would have died silently.fa0325d3clone_for_transferwith its trigger. Only PROPFIND re-negotiated onstale=true; every data path mapped a 401 to "Session expired", so an Apachemod_auth_digestserver rotating its nonce at the default 300s lifetime failed a transfer that was perfectly authorised. The blocker was not the missing retry but that the retry could not be decided:DigestChallengehad nostalefield at all, andrenegotiateaccepted any parseable challenge while documenting itself as adopting only a rotated one. A revoked password answers with a challenge that parses, so wiring a retry onto that gate would have been the retry storm the debt was deferred to avoid.staleis now parsed (bare, quoted, any case) and a challenge is adopted only when it saysstale=trueand offers a nonce that differs from the one held, which also bounds a server that echoes a dead nonce. On that gatestat,checksum,keep_alive, the Nextcloud chunked MKCOL and the chunked part PUT replay exactly once, and the repair is pool-wide because the challenge is shared.listis deliberately excluded: it goes throughsend_propfind, which already re-negotiates. Live validation against a Digest server that rotates its nonce is still outstanding and is not claimed.35a420d9i18n:validatenever caught them because the values are present and are not placeholders: it checks that a value exists, not that it is not English. The naive test is useless here, "identical to English" fires on 15992 pairs across 1093 keys and almost all are correct (FTP, AeroCloud, us-east-1, ARNs). The detector keeps identity as the trigger but takes its evidence from the shape of the English source, so it narrows to 1966 pairs across 69 keys, a list a human can read. 1899 values were then translated across 46 locales and the gate ships at--strict, not as a ratchet: the backlog is zero and cannot come back.078574cf--jsonbranch calledprocess.exit()immediately afterconsole.log, which abandons whatever stdout is still queued, and stdout to a pipe is queued, so past the 64KB buffer the report is cut mid-string and the caller receives unparseable JSON. Latent rather than theoretical: today that gate reports 0 new hits so its output never reaches the buffer, and it was found only by hitting the same wall in a sibling gate whose report is 317KB. Both the--jsonand the human branch now setprocess.exitCodeand let node drain, so the shape that causes it is gone rather than the symptom.5a309134UNAUTHORIZED, so it found only the sites naming the status and missed every site letting a 401 fall into a catch-allstatus =>arm, which is most of the provider. Measured against Apachemod_auth_digest, a recursive download of 60 files lost 52: eachGETmeeting an expired nonce failed as "Authentication failed during transfer" and was never replayed. Upload, ranged reads, resume,DELETE,MOVE,COPY,MKCOLandUNLOCKshared the gap. A shared helper now takes a closure that rebuilds the request, so a replay recomputes its ownAuthorizationheader and never re-presents the dead nonce; the singlePUTrebuilds its body by reopening the file rather than buffering it, which also guarantees the replay starts at offset zero. Replaying on 401 is safe for callers that are not idempotent because a 401 means the server rejected the request before applying it, which is what separates it from replaying a timeout. A second defect surfaced only under parallel transfers and no unit test could have shown it: the "adopt only a nonce we are not already holding" rule is what prevents a revoked password becoming a storm, but a sibling worker often adopts the rotation first, so the server offers back a nonce the shared challenge already has,renegotiatecorrectly declines, and the request that lost the race dies holding a dead nonce. A replay is now also granted when the held nonce differs from the one the failed request carried. The anti-storm property is unchanged: when nothing rotated both arms are false and a bad credential still costs exactly one request. The live validation row 6 left outstanding is done and reported below.27cb17e6scripts/gen-provider-inventory.tsderives them intodocs/PROVIDER-INVENTORY.jsonfrom the catalog and the registry, and a--checkgate in the fastcheckslane fails when the committed file stops matching the code and prints which count moved. Nine stale counts corrected along the way, including the two nobody opens: the AppStream metainfo that GNOME Software and KDE Discover render, and the Snap Store listing.1aacdd10aeroftp checkpointscould already clear it per endpoint, but nothing in the app could: a Tauri command forforget_endpointhad been added during the v4.1.8 audit and removed again in the pre-tag pass because nothing called it. The screen brings the listing too, since the four identity values are matched exactly.38abc2c544a4c1a07d517b03enforce_capcalls sharing a saturated store of same-second timestamps could pick each other's just-persisted opening record, because the sort was terminal-first then oldest and equal timestamps fell through to readdir order. A grace window makes a fresh record sort last; a lock file would have serialized every multipart open.b5db68e7stable: falsewith no recorded reason, which is indistinguishable from a known defect and is how both stayed hidden. S3Drive was verified live (connect, list, put, byte-identical get, delete); DigitalOcean was never tested because the trial account expired, and that is now written down instead of implied.c1586d1alistfolderreturnsresult: 0whiletrash_listreturnsresult: 1000 "Log in required.", identically as a Bearer header and as anaccess_tokenquery parameter, with and withoutrecursive, and with an explicitfolderid=0. A controllistfolderfired between two failing trash calls proves the token valid at that instant, anduserinfo?getauth=1mints no session token for an OAuth caller, so there is no fallback. pCloud OAuth exposes one optional permission,manageshares, so no scope and no re-authorization can lift it, and rclone does not implement CleanUp for pCloud either. The affordance is removed, mirroring theinternxtentry already disabled in the same mapadbf0393stable: falsenot for a known defect but because the trial account used to verify it live had expired, and an ordinary S3 endpoint dispatching through the same stack as the other forty-eight presets, with no provider-specific code path of its own, does not warrant gating on that. The explanatory comment goes with the flag, since a note about a deferral that no longer exists misleads the next reader. Set totrueexplicitly rather than deleted: every consumer testsstableas falsy, so deleting the field would have left the preset filtered out and dimmed exactly as before, minus the warning badge. All forty-nine presets now carry the fielde65f0603<data root>/ui-tokens.json, withdocs/UI-TOKENS.mdas the contract; validation rejects rather than sanitises and reports every dropped entry with its reason. The status colours are deliberately not overridable, because a user who recolours them can make a destructive-action warning indistinguishable from a confirmation.64439be8mainwent red on a yanked crate, and the lock moved by four lines rather than by a resolver pass. crates.io yankedchacha200.10.1 and the audit step denies yanked crates, so this was never a vulnerability. Worth recording why the pull request merged just before it was green whilemainwent red, because it looks like an escape and is not:cargo auditruns withcontinue-on-erroron pull requests and authoritative on push tomain, deliberately, so an advisory published against a transitive dependency cannot red-wall an unrelated PR. The yank landed between that PR's run and its merge, which is the mechanism working rather than failing. The lock carries twochacha20majors and only one moved: 0.9.1 comes fromchacha20poly1305, which requires^0.9and is untouched; the yanked 0.10.1 comes fromrandandssh-cipher.cargo update --precisewas rejected because it also re-resolved two unrelated dependencies, downgradingsyninsidedata-encoding-macro-internalandgetrandominsidetempfile: both resolutions are valid, neither belongs to this fix, and a dependency change should be the change it claims to be. Verified bycargo metadata --locked,cargo check --lockedand a cleancargo audit, not by the absence of an error.b5dea635uiTokensTitlewas translated in three locales and left English in the other 44: "UI tokens" is the name of a documented contract indocs/UI-TOKENS.md, so a reader who sees it translated in the panel and searches the docs for it does not find the link;ja,koandzhgo back to the English term and all 47 now agree. "Reset to defaults" cleared the overrides from the running document and leftui-tokens.jsonuntouched, so the reset lasted until the next start: the label changed rather than the behaviour, because that file is written by hand and a button labelled reset that silently deletes fifteen tuned overrides is worse than the ambiguity it removes. It readsClear overridesnow. The CodeRabbit pass on the same change was verified finding by finding rather than accepted or dismissed in bulk, and it split four ways: two applied that looked like nitpicks and were real, Croatian and Icelandic each using two different words for one thing inside a single screen, with the Icelandic panel offering to clear titles becauseyfirskriftis a heading andyfirskrifunan override; one rejected on measurement, the Portuguese finding citing two words that do not appear in the string it names; and two left unapplied, Welsh and Swahili, because the correct term could not be verified and inventing one is the error the pass exists to prevent. Those, plus Icelandic, are queued for a native reader.e8768e44inventory --checkcompared only thecountsblock of the snapshot against the live counts, so an artifact that had drifted inside its own lists passed with "OK (counts match)":countsis exactly the part that still matched. The invariants that would have caught it already existed as a test, but they ran on the document built in memory and never on the artifact on disk. They are now a shared non-panicking function called on the deserialised snapshot, reading every figure by explicit path, with a missing or wrongly typed field treated as a declared violation rather than a silent zero, because a figure that disappears is exactly the drift the gate exists to see. Proved by differential, not asserted: on a snapshot with one command removed fromtauri.commandsandcountsleft byte-identical, a binary built from the parent commit reports "OK (counts match)" and exits 0, this one names both broken invariants with their numbers and exits 12. Two review findings were verified and applied, and one of them was the change auditing itself: a magnitude tripwire (tauri > 500) inherited from the in-memory test had been promoted, unnoticed, into an invariant of an artifact, so a legitimate surface of 450 commands would have been declared inconsistent by the very change written to stop wrong diagnoses. It is back in the build test where it belongs. Six tests ship with it, one of which reads the published artifact from disk socargo testverifies it and not only CId42879b4b2_list_buckets, already called when a session opens, returnsdefaultServerSideEncryptionandfileLockConfiguration; neither was deserialised, soHEADER_BUDGET_STDstayed hardcoded at 7000 while B2 lowers the cap to 2048 on a bucket with server-side encryption or Object Lock, and the_STDsuffix showed the smaller budget had been known and never implemented. From 2026-09-14 Backblaze turns SSE-B2 on by default for new buckets, so the smaller budget stops being the exception. The two triggers are read independently and are not symmetric: encryption fromvalue.mode, Object Lock fromvalue.isFileLockEnabledand only from there, because B2 documents a state where the lock is on while its default retention is off and deciding fromdefaultRetentionwould call exactly that bucket unlocked. Each setting has three explicit states and "not read" is a real one, since B2 gates both behindisClientAuthorizedToReadand a bucket-restricted key is told nothing; an unread setting keeps the larger budget deliberately, because B2 is the authority on what it accepts and refusing a path it would have taken breaks a working setup with no way for the user to override. The fields are parsed as raw JSON rather than typed, because Backblaze's own documentation contradicts itself about their shapes and a parse error would fail the bucket listing, that is the connect: trading a wrong budget for a broken connection is a far worse deal than the defect being fixed. Two limits are declared rather than hidden: the documented measure for the standard budget is the fully encoded header line including the CRLF, which this check does not count, and since file names are separately capped at 1024 bytes a legal name reaches at most 3166 bytes even encoded, so the refusal path cannot currently fire at all and what shipped today is the diagnosis, not the refusal. Four review findings were verified and applied, one of them found by writing the test rather than by the review that prompted it765ef41cFtpManagereach carried their own parser and they had drifted apart. Both now consumeproviders/ftp_listing. The legacy copy had no failure mode: any line it could not parse became an entry, and any such entry without a dot in its name became a directory, sototal 12was listed as a folder. An unrecognised row is now an error the caller skips instead of an invention. The five parser defects were moved into one place, not fixed, and are the subject of the next row's sibling work81fd3d9fmkdiron an existing directory collapsed into a generic failure, transient421and450were reported asInvalidPathso a session problem read as a wrong path, and messages named neither the operation nor the path. Verified live on a vsftpd 3.0.5 server over explicit FTPS: a missing path now errors while a genuinely empty directory still lists clean, on both the CLI and MCP, andmkdirreportsPath already existswith the pathf71fc5ddLISTwhose every row failed to parse still returnedOkwith zero entries, which downstream reads exactly like a wiped remote. It now fails, so the scan counts a listing error and the delete guard refuses through the arm that names the real reason rather than reporting "source side is empty" and sending the reader to look for a deletion that never happened. A partially unreadable listing is not covered and is recorded as suchd521dc55<abspath>text without decoding it, and Deleted dates were being clipped by a revision mtime read after the attribute that carried them. Separately, an imported refresh token is now bound to the record it belongs to, andprofile_idis stamped from the saved record with options-borne values dropped at one choke point so the CLI, MCP, agent and AI tool paths agree instead of one file enforcing a policy the next one reverses.renameno longer reads a 404 on the live path as evidence that the object exists only in the trash, which could rename an unrelated object of the same name inside the trash and report success. Filen quota errors keep their REST cause instead of collapsing into "No user info data", so an expired token no longer presents itself as a missing quota. Two limits are recorded rather than hidden: two added tests pin their helper and not the call site, which needs a vault and a live connection unless the config assembly is extracted into a pure function; and this closes part of the report, not all of it4099cc2fis_dirwalks into a symlink wherever the two flags derive from different facts, and across the provider tree that happens on exactly one provider, SFTP, whereis_dircomes from the link's target whileis_symlinkstays true. The other sites cannot diverge today and are converted anyway, because a single site that still requires reading costs that property for all the others, including one that is already correct through a guard a grep cannot see. The count is stated rather than implied and was measured twice, with the command in the pull request so anyone can redo it, and the boundary declared: the same pattern exists outsideproviders/and this change does not touch it. The first version of the change claimed the grep already returned zero and it did not, which is why the four remaining sites each carry their own reasonf6089243rsync -cfull checksum read is replaced with-I, keeping whole-file integrity through the delta trailer without reading the source twice, and a source that grows past its declared length now fails immediately instead of waiting for an EOF that never comes.9d1700bbb41dfc2c47c2e707timetook the clock-skew retry, and" 500"matched" 5000"and" 500 MB". A failed MEGAcmd command containing the wordalreadywas reported as success, which lost errors rather than spending retries. And a refused TLS port was recognised by an English string, so the skip never fired on a localised system; it now reads the typedConnectionRefused.588ffa7eCWDon a missing directory reporting success.fbbe288dbroken pipewas filed under "Disk full or I/O error", which is non-retryable, and the four executor sites break on that, so the most ordinary way a server drops a transfer (an idle timeout, a NAT eviction) was the one failure never retried.i/o errorwas in the same list and is not a symptom at all but the label a parser prints in front of the real error, so a connection dying mid-response became a permanent disk failure on WebDAV, S3, Azure and Jottacloud, and therefore on every service configured over those transports: Nextcloud, ownCloud, MinIO, Wasabi, Storj and the Discover presets.18d4a9a1cannot find, the phrase Windows itself uses for a missing file, was known to one reader out of five; a bare FTP550was read as absence when it also means a refusal; a cancellation phrased without "by user" was a cancellation to two layers and a failure to two others; and a revoked Yandex token never triggered the credential reload because the guard matchedinvalid tokenwhile the provider emitsinvalid_token. Each question now has one predicate, and the auth one normalises underscores so neither spelling of any term can be the one nobody thought of.80f470a8?cphash=true, because?mv=and?mvDir=return 404: the Trash path is not an object, the listing being a virtual view. Sync templates now carry verification, compression and canary, so importing one stops resetting them to defaults. The servers table clears its selection on drag start and gives cells stable ids. Rclone-obscured passwords are revealed before use and an emptypassword2is treated as the default salt, checked against rclone 1.74.3 in both directions. Folder restore is classified correctly and has NO working JFS command, which is declared rather than glossed. Reader-facing issues this row closes, named here because the row did not name them and a tracker nobody can search by issue number is a tracker with a hole in it: #514 sync templates resetting verification, compression and canary on import, #600 rclone-obscured passwords and the emptypassword2salt, and PART of #397 Jottacloud trash. The gap was found while re-labelling those issues: the fix was in the release and the tracker recorded the commit, but not which reports it answered, so nobody reading either one could get to the other.4099cc2f550 /no such/keep.txt: Permission deniedmatched on the PATH and a refusal read as an absence, in the guard the speed tool uses as permission to overwrite and then delete. The caller's path is now removed before matching, which the FTP provider had done since the404tightening while the other four callers never received it. A negated cancellation (could not be cancelled, and the contracted forms with either apostrophe) is no longer a cancellation. The retry loop and the failure it builds now ask the same question. And a missing OAuth scope on Yandex stops telling users to regenerate a token that works.9c38bf38tauripin stays and its documented reason was wrong, which is what sent readers looking for the remedy where it cannot be. 2.11.1 introduced no check classifyinghttp://127.0.0.1:*as remote: the Linux branch ofis_local_urlis identical between the two versions, and what changed is one line that starts APPLYING a verdict that was already false. The obvious remedy dismantles its own precondition, because declaringfrontendDistas a URL empties the embedded assets thattauri-plugin-localhostexists to serve. It is all or nothing because theinvoke_handlerregisters 1013 commands and one permission file makes the ACL govern every one of them. The auditors' file also carried a false absolute, corrected and scoped to the platform it holds on. Comments only, zero code lines.9ed325fb550had been sent and was sitting in the client's own receive buffer, because reading the data channel means not reading the control one. The read now watches both. The first digit of a waiting reply is read withpeek, which does not consume:4and5end the wait, anything else stops the watch and leaves the reply in place, because the completion reply arrives on that same channel and treating any reply as a refusal would abort a working transfer and truncate the file. The loopback fixture cannot reproduce this, since there the data socket closes first and the wait ends by itself; that is written next to the tests as well, because the tests are where somebody would read a green and draw the wrong conclusion999ba7fbread_range. That site was converted first precisely because it is the only one that could have falsified the shape, and it went from 45 lines to 8. The two divergences, counted by listing every?the callers have between opening the channel and releasing it: a failure while collecting a queued refusal, and a local disk failure in the caller's own body. Both used to leave the session alive in a state nobody had established, so the next command could read the previous answer as its own; both now discard it. A session thrown away costs a reconnect, a session in an unknown state costs a wrong answer delivered as a right one576b8abchashsumon/pippo.txt\r\nDELE /vittima.txtmade the fixture log aDELEnobody requested. The reachable vector is a path supplied by the caller (CLI, saved profile, API) and NOT a filename from a hostile server's listing, which was checked rather than claimed, because a listing is line-based and a CR inside a name splits the line before the parser sees it. Pinningsuppaftpto10.0.2refuses the write, but the bump alone would have swapped one defect for another: the library reports the refusal as a connection error, so the sentence blamed the link for the name and the substring matcher inclassify_sync_errormarked it retryable, meaning a refusal that can never succeed would have been retried to the limit. The decision now reads the io kind in the one place that already types these failures, so the sentence and the retry decision are fixed by the same change456d7b83DELEwas never sent and a total cannot say which verb is missing. Validated against the fault it exists to report, on both binaries: with the injection still possibleDELEis 1 and the assertion fails, with the refusal in place it is 0 and it passes. TheUSERcount in the same reading is the control, without which aDELEof zero could equally mean the client never reached the server at all and the assertion would pass for the wrong reason2bd1d2f0ErrorKindrather than from how the operating system phrased it. The strongest guarantee is an absence: there is no hand-written constructor for the not-found variant, so it can only be born from a kind and never from a call site's opinion. Review also found the one place the new invariant does not hold, three usage-error exits that override the class on purpose, now carrying a line saying so and why2fb4bbb4stat, so a file sitting where a directory was needed produced a failure one level below the real obstacle, with a message naming the wrong path. Reading the entry's own answer fixes that and immediately breaks something else:is_dir == falseis not proof of a non-directory, because both FTP parsers report a symlink to a directory that way (LISTmatchesdand a symlink row starts withl;MLSDacceptstype=dir,cdirorpdirwhile a symlink arrives asOS.unix=slink). A symlinked/var/www/html, ordinary on shared hosting, would have been refused outright where it previously worked. The remedy is a type rather than a corrected branch: the function exits early only on positive proof that the parent is a directory, and anything else falls through to the walk, which is what happened before the check existed. The mock had no symlink dimension at all, so the entity that breaks could not be seeded and the tests were silent about it rather than wrong; it now can be, and a test pins it9dd0040frusshas a client, which our own security gate is structurally unable to see. The headline one lets a hostile or compromised SSH server trigger channel-scoped handler callbacks for channel IDs the client never opened, producing panics, forged exit statuses and desynchronised channel state (GHSA-47hw-gvq5-r2gm, High, CVSS 7.5). The other two are a missing zero-point validation for X25519 in the hybrid ML-KEM768 exchange, and a panic withmac=noneand a block cipher.cargo auditis green here and cannot be otherwise:russhhas no RustSec entry, so its advisories live only on GHSA and the tool has nothing to look at, which means a gate blind to a whole class reports the same green as a gate that looked and found nothing. That limitation was already written intoCargo.tomlat the previous bump rather than discovered here. The absence of a fix on the 0.62 line was settled with dates instead of a changelog: 0.62.7 predates the advisories it would have to fix, socargo updatecould not have reached a patched version on that line whatever it was askedbac8cbe87c25f517classify_data_failureturned any remaining 5xx intoInvalidPath, so[530] Not logged in (while reading /srv/data/report.csv)sent the reader to check a path that was never wrong. Only550and553are replies about the name itself, so only those stayInvalidPath. The half deliberately left out is recorded rather than forgotten: narrowing the noun also changes retryability for ten codes, because the label was the only thing holding them non-retryable, so removing it alone would make a full storage quota and a lost login retryable and they would be retried to the limit. That half belongs with teaching the classifier to read the type, not with widening needles to include the bracket, which would be curing the disease with the disease. Split out of #671 so the security fix could land whilemainwas red90657df3classify_sync_errorlooked for552,403,550,404,401,530, a status followed by a space, while every failure the FTP provider renders puts a]there:[552] Exceeded storage allocation. Quota, permission, not-found and authentication were decided by rules that could not see the shape the system produces, so on production-shaped strings they classified asUnknownand retryable, and a permanent 5xx was attempted three times. They were not uniformly dead, which is worse than dead: a server that echoes its own code inside the text made the old needle match by accident, so the rule held against one server and silently stopped against another, and the true observation supported a false conclusion.mentions_ftp_statusnow also knows the bracketed shape, anchored rather than searched, so a file named[404] note.txtcannot be read as a status. Stated up front because "revive a dead rule" reads like a no-op: this moves behaviour for the majority of servers, in the direction where a second attempt could not have succeeded3f5b6166classify_keystore_errorsearchede.to_string()for phrases while its own doc comment promised pipelines could branch on the code without parsing text, so three sites that interpolate a caller-chosen string made the exit code a parameter:--merge decryptreturned 6, an authentication failure, and--merge "io error"returned 11, a disk fault, for the same invalid strategy. A crafted compression marker inside a backup file could steer it too. The first version of this pull request declared two changed codes and changed six, because it rested on a count of nineteen construction sites read as eight from agrep | head -8, with the truncation added by the same hand that then trusted the number; running the old chain over all nineteen exposed four regressions. The remedy is one floor up:Encryption(String)was swallowing failures that already had a type, so the store sites map toIoand the codec gets its own variant. Both halves are pinned by breaking them. Nothing in this repository branches on these codes, checked with a control since 23 numeric exit-code comparisons do exist elsewherea2f9af40@typespackages whose upstreams now ship their own declarations. Removing a types package is the change that breaks at a call site nobody was watching, so it was verified rather than assumed:dompurify@3.4.14carriesdist/purify.es.d.mts,react-window@2.3.0carriesdist/react-window.d.ts, nothing undersrc/names either@types/package, and after removaltsc --noEmitresolves both from the upstream declarations, read out of--listFilesrather than inferred from an exit code. Declared limit: both lockfiles are excluded from bot review by path filter, so the largest part of this diff by line count carries a human reading only4eca11ecthumbnailCache.tsheld literal NUL characters inside a cache-key separator; they are now written as an escape, with a comment recording why it must stay so a later cleanup does not turn it back into a literal. The diff is zero lines added and zero removed, and the reason is sharper than it looks: with the NULs inside the first 8000 bytes git classifies the file as binary, so this change renders asBin 4189 -> 4441 byteswithBinary files ... differ, and every earlier change to this file reached review as an opaque blob instead of as lines. It is also why the review bot reported "no reviewable changes" and never read it. After this lands the file is text again and the next change to it is reviewable. What stands in its place: the seven existing tests pass unchanged, which is what shows the key format did not move, and no other file undersrc/orsrc-tauri/src/has the same problem1af67666cargo check -p aerovaultpasses against this application's own feature resolution. MPL and GPL combine here only because aerovault carries no Exhibit B "Incompatible With Secondary Licenses" notice, verified absent in this release. If that notice ever appears upstream the combination stops being permitted and NO BUILD WILL FAIL, so this is a reading to repeat by hand at every aerovault bump rather than something a gate can hold5525b5a6drive-only token instead:/2/profile403 requiringuser_info,/2/drivewithout the parameter 422 "The account id field is required",/2/accounts403,/2/drive/init200 returning anips.uuidand not an account id. Soaccount_idreally is required and the remedy is a differently scoped token, not a different endpoint, and that 200 is recorded next to the code precisely because it would send the next reader down a road that does not exist. The message now names the missing scope, read fromerror.context.scopesand not from the prose description, with a fallback for a refusal that names none, because a defect that returns for a subset is harder to see than one that returns for all. Raised in #650203157f1GPL-3.0SPDX id is replaced byGPL-3.0-or-laterin the README,Cargo.toml,tauri.conf.json,snapcraft.yamland two source headers. It began as an uncommitted edit sitting on a UI-tokens branch, where a licence field would have reachedmaininside a pull request about button labels and nobody reviewing that diff would have been looking for it. The last two declarations were found by enumerating the files that declare a licence, not by grepping the old string, which is the search that had already missed them once. No gate covers this in either direction: the suite asserts nothing about SPDX values, so a wrong identifier is invisible to CI and only a reading finds it529882455ea980f1acl_fs.rsand a packaging assertion that the shipped profile carries-A. Owner, group and device files stay out of scope and are still listed as not implemented. Logged after the fact on 2026-09-01: this row did not exist while the release was being prepared, and the omission had a visible cost. The README went on saying ACL was not implemented for the whole cycle, because nothing in the tracker recorded that it had shipped, so no reader had anything to check the sentence against. Found by diffing the 55 cycle commits against the tracker's own SHAs rather than by reading either list on its own15401f02t('...')may appear in the chip, which would have blocked this change while permitting a worse one, a key invented rather than reused. It now pins the property that decision protected. Logged after the fact on 2026-09-0189830ea310563308fd80153dfoo/../barandbarare two different objects, and the path normalizer resolved the dot segments anyway on the path of nine call sites includingrmdir_recursive,renameandserver_side_copy. A recursive delete of the literal prefixfoo/../bar/was sent asbar/and removed objects the caller never named. The function's own comment already explained that collapsinga//bintoa/bwould point rename, copy and delete at the wrong one; the reasoning was applied to half the cases. Resolution moved tocd, where a relative segment means what the user means by it. WebDAV: the 425 retry loop had three attempts and onereturninside it, and a rotated Digest nonce arriving on the FINAL attempt issued acontinuethat skipped it, exhausted the range and reachedunreachable!, panicking the task; both single-stream GET paths route through that helper. Its comment likewise said the replay is tracked by a flag "rather than by attempt number: the 425 loop may already have burned attempts". The replay now gets an attempt of its own, bounded at four. Both regression tests were observed FAILING first, which is the only thing that makes them regression tests:left: "bar", right: "bar", the collapse itself, andentered unreachable code, the panic reaching the caller5f2d57bfBATON-prose-vs-inventory-gate.md, because the same figures also live inside shields.io badge URLs and a gate written tonight would have read the prose and stayed green on three numbers encoded in a linkb269a4a0ProviderTypeunion with a regex matching a SEQUENCE of| "member", and a sequence stops at the first piece that is not one, so a doc comment abovemtpended the match atpeer. It read 34 of 35 while asserting onlylength > 20, so the loose threshold hid the short read; the reader now refuses to return a short list. The third: the gate needs an accented twin in the same file, at least 4 letters and 3 times as frequent, soaterstallningsfil(2) againståterställningsfil(3) scores 1.5 and stays silent. Found by folding every ASCII word and looking for the accented form of a SEGMENT elsewhere, then read one by one against the English. Restorations only, never rewordings: where the ASCII form is a real Swedish word it was left alone, which is most of what such a search proposes73b312cecargo testwith a filter that matches nothing prints "running 0 tests", then "test result: ok." and exits 0, so a lane that proved nothing is indistinguishable from a lane that proved everything. The dead one is the SFTP password-only fallback lane: the test it names by--exactwas renamed on 2026-05-16 inbb20f6a6cand the workflow line was never updated, so for three and a half months it stood up a Docker fixture, waited for SSH, ran zero tests and reported green. Read from the run log onmain, not deduced: the step printsrunning 0 testsandtest result: ok. The class was closed rather than the instances: of fourteen points that select tests, two were already protected and twelve were not; all fourteen now call onescripts/assert-tests-ran.sh, with=Nfor a step that names a single test and "at least N" for a suite meant to grow, because an exact match on a growing suite goes red the day someone adds a legitimate test, which usually gets answered by deleting the check. Thresholds are what a named green run actually executed, not estimates. Both gates were watched going RED with the condition made true, including the realistic way it breaks: removing the two#[ignore]attributes, which looks like an improvement5532e49cTransfer failed: connection reset after 550 of 1024 bytestherefore classified as permission denied and NOT retryable, and both executors stop on!retryable, so the file was discarded. The half the audit did not predict is the opposite direction:Upload failed (403): the account cannot write hereclassified asUnknown, because the rendering... failed ({}): {}puts a bracket and a colon there and never a space. That form is produced by kdrive, drime, internxt, fourshared, zoho, pcloud, koofr, jottacloud, box and s3, andHTTP {}: {}and... failed with status: {}miss for the same reason. So the rule was blind to the most common rendering AND firing on quantities. The criterion moves from "the number appears somewhere" to "the number sits where a provider renders a status", with the accepted positions read one by one from the realformat!calls, and the non-status numbers all sitting after a quantity word. The comment in the code had said this was waiting on a missing measurement rather than on a judgement: the measurement was taken. A test we already had caught an error in the fix:[was briefly among the accepted prefixes, anda_path_that_looks_like_a_status_does_not_decide_the_classificationwent red, because a file a user names[404] note.txtwould have gone back to deciding the classification503b5638150and the final4xx/5xxin ONE segment, the read that collects the first pulls both into the reader's buffer, and the bare socket underneath shows nothing. The watcher was peeking that socket withMSG_PEEK, so it was structurally blind to a refusal already sitting one layer up, and the transfer fell through to the data idle timeout. The observation point was wrong, not the case, which is why the remedy exposes the buffer rather than widening a heuristic or raising the timeout. Work by Kimi K3, and the object of value is the red: a test that fails on the pinned base for the predicted reason, asserting that the refusal is in the buffer AND that the socket is empty, with the data channel held open and silent. The vehicle matters too: a Docker fixture on loopback could have shown nothing, because the file itself records that there the data socket closes at once and the wait ends by itself, so the silence would have read as "does not reproduce". This ships a one-method fork of suppaftp, pinned by rev, because the crate keeps its reader private andget_ref()returns the socket UNDER the buffer; the exit condition inCargo.tomlnames who must file the upstream pull request and states that it is not open yet. Under TLS the buffer holds plaintext, so the reply class stays readable where the socket bytes are notf9cb0689enforce_capcounted aNotFoundfromunlinkas a successful removal, so two concurrent opens planning one eviction each could both select the same oldest file: one deleted it, the other counted the failure as its own success, and both persisted, leaving the store one over its cap. That is fixed: a vanished candidate is counted separately and the loop moves to the next one, so the cap holds. What is NOT in this change, and why, is the useful part. Two further remedies were written and then reverted, each having introduced a defect worse than the one it closed: reserving the slot on disk before the cap made the rollback delete a record a concurrent opener had already adopted, and narrowing that rollback to "only if the on-disk bytes are still mine" still could not tell MINE from ANOTHER IDENTICAL, because the record carries a second-granularity timestamp and no nonce, so two openers of the same transfer in the same second serialize to the same bytes. The residual gap ships knowingly: it already exists onmain, it is bounded to one record, and it is reabsorbed by the next cap that succeeds, while both remedies could lose a checkpoint an opener had been told was durable, in silence. The concurrency test was removed rather than weakened: it could not distinguish the correct case from the broken one, since both overshoot by one, and an assertion that passes in every case reads as coverage without being itfa4facdaRequested / under consideration
Community requests and internal follow-ups being evaluated for this or a later release.
Effort: 🟩 quick win · 🟧 medium · 🟥 large / epic. Ordered by effort (quick wins first).
-iselectors (prioritised by the reporter ahead ofEdit(E)and the 2-letter semantics; exact behaviour to be pinned).jpgto.jxltranscode in AeroFile: JXL re-encodes an existing JPEG losslessly and reversibly (the original JPEG bytes reconstruct exactly), for a real size win with no quality loss. Candidate framing: AeroFile as a GUI for libjxl.icnsmulti-size (the v4.1.0 ones are single-resolution drafts). Needs a Mac to build and live-testparticipants/addcontactUUID format (currently a best-effort workaround), investigate the Filen free-plan 10-note limit ghost-note cleanup, and add a rich text editor for the rich note typemalloc(): unaligned fastbin chunk detectedwhile deserializing a D-Bus message, the classic GLib heap-corruption signature (that worker is the victim tripping over an already-poisoned fastbin, not the corruptor). Two marshalling fixes already shipped and are safe: tray badge (31db84f8b) andapp_ready+ splash-timeout (cae95ee91), present since v4.0.5. But the SIGABRT STILL recurs (v4.0.9 on 2026-06-25, and the v4.1.2-era build on 2026-07-05, identical signature): the marshalling is INCOMPLETE, a still-uncovered off-main-thread GTK path remains. Sync Tauri commands are provably main-thread (safe), so the suspects are the event/plugin callbacks whose thread context is unconfirmed: trayon_menu_event/on_tray_icon_event, the single-instance plugin callback, andon_window_eventCloseRequested. Pre-existing across versions, intermittent, triggered by D-Bus storms on suspend/resume or monitor standby; it can corrupt the whole app until restart. Definitive path (owner-approved): adopt self-hosted crash reporting with native minidumps to capture the real corruptor, plus an ASan/valgrind repro, then finish the marshalling on the remaining callbacksHKCU\Software\AeroFTP\Settingsand re-read byNSIS_HOOK_POSTINSTALLso an in-app update cannot silently restore it (POSTINSTALL runs on every install, and an update is an install), plus a/NOASSOCsetup switch. A visible components-page checkbox is out of reach without forking the Tauri NSIS template, which is why it is parked rather than half built. Raised in #454; deferred because AeroFTP never claims the default handler for the general formats, it only adds itself to "Open with" plus its own context-menu entries, so with the uninstall residue fixed (queued #59) what remains is menu visibilitytsccom.aeroftp.AeroFTPtoapp.aeroftp.AeroFTP: DEFERRED behind other priorities, kept here so it is not rediscovered. Not a cosmetic rename: the identifier is the Flatpak id, the desktop file's identity, the autostart entry, the installed package's identity, and the key under which existing users' saved credentials live, so shipping it needs a migration path for everyone already installed. The existing work is less than half of it: the branch touches 9 files, whilemainnamescom.aeroftpin 68, of which 47 are locale files and 21 are not (includingcredential_store.rs,file_associations.rs,lib.rs,portable.rs, the CLI,snapcraft.yaml, the NSIS hooks, the updater policy and the two deb scripts).app.aeroftpappears nowhere onmain. Merging the branch as it stands would leave the app half renamed, which for an identifier is worse than either end state: the keyring would look for one service, the desktop file declare another, the updater a third. The work exists as a single unpushed commit696a3d02dated 2026-07-12 on the L2 workstation, branchchore/app-id-rename-app-domain, worktreeaeroftp-appid-rename, and nowhere else. It survived the branch cleanup of 2026-08-31 only because it had never been pushed, so no audit oforigincould see it and it was not on the deletion list either. Found by cross-checking the deleted branches against L2's local work, asking the question that no list suggestedKnown issues found after release
Reported against v4.1.7. Carried over from the #1415 tracker where still open.
listfolderreturnsresult: 0whiletrash_listreturnsresult: 1000for the same token on both transports, with and withoutrecursiveand with an explicitfolderid=0, anduserinfo?getauth=1returns no session token for an OAuth caller. pCloud OAuth has no trash permission to request (managesharesis the only one), so re-authorizing changes nothing, and rclone does not implement CleanUp for pCloud either. The View Trash affordance is removed for pCloud rather than left as a control that cannot work. Carried over from #1414 known issue #4, then #1415 known issue #180f470a8against a real server: the CLI and the MCP surface both now refuse withInvalid path: [550] 550 Failed to open file.naming the file, in 33.1s and 30.2s, ended by the client and not by a harness cap. The wait is bounded, not short: those 33 seconds are the opening budget being spent. The listing issued straight afterwards on the same profile, which previously never returned because the stuck download held the pooled connection, comes back in 0.4s. Full table in the results comment.fix/introhub-347has no functional hunk left behind. Catalog badges, generic-server order and per-method glyphs, and form-tab drag reorder all reachedmainthrough focused commitsbb5a53c8,fea55779,b5218d29and6a78daa2, with shared ordering/helpers and regression tests. The branch-onlydiscoverData.tsexport is an unused duplicate. Its remaining icon remap is a rejected alternative, not missing work:maindeliberately preserves the shipped #567 OAuth/S3/FTP/SFTP assignments and documents why S3 staysDatabaseand Blob usesContainer. The parked branch is superseded and safe to retire after this audit; it was not deleted hereStatus legend: 🔴 confirmed · 🟡 investigating · 🔵 planned / idea · 🟢 done / fixed (pending release) · ✅ released
All reactions