Recent changes to this wiki:

add news item for git-annex 10.20261113
diff --git a/doc/news/version_10.20261113.mdwn b/doc/news/version_10.20261113.mdwn
new file mode 100644
index 0000000000..665c8dd816
--- /dev/null
+++ b/doc/news/version_10.20261113.mdwn
@@ -0,0 +1,24 @@
+git-annex 10.20261113 released with [[!toggle text="these changes"]]
+[[!toggleable text="""  * Added transfers command, displays a live view of transfers.
+  * Don't prompt for verification of proxied remotes, which is not
+    necessary and didn't work. (Fixes reversion in 10.20261026.)
+  * p2phttp: When --directory is used to serve a large number of
+    repositories, limit the amount of git helper processes and open files.
+  * Fix buggy handling of a submodule in a linked git worktree.
+  * Fix bug in parsing the P2P protocol that caused breakage when filenames
+    contained unicode multibyte space characters.
+  * Fix mimetype and mimencoding detection of non-ascii filenames
+    in non-unicode locales.
+  * Avoid display issues with multiple progress meters when not in a
+    unicode locale.
+  * Botan is enabled by default; removed build flag.
+  * git-annex.cabal: Switch from magic to libmagic.
+  * Allow the MAGIC environment variable to override the bundled magic file
+    location.
+  * The Linux arm64-ancient build has reached end of security support
+    and will be removed after this release. Android users are recommended
+    to switch to running `pkg install git-annex` inside termux.
+  * NoLLMDependencies: Update for network and http-semantics.
+  * Fix build when the Memory build flag is disabled.
+  * Fix testremote failure with p2phttp remote.
+    (Reversion from 10.20261005)"""]]
\ No newline at end of file

update
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_7_d481e4106d3bfd4a21d4ed111d1eb03d._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_7_d481e4106d3bfd4a21d4ed111d1eb03d._comment
index 03bc50e9c3..5090d590ad 100644
--- a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_7_d481e4106d3bfd4a21d4ed111d1eb03d._comment
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_7_d481e4106d3bfd4a21d4ed111d1eb03d._comment
@@ -21,12 +21,24 @@ I think that --proxyconnections can stay as an idle connections cache
 limit rather than an upper bound on the number of active connections.
 But make it be across all served repos, rather than per-repo.
 
-Add a cap on the number of total proxy connections. It could be the same as
-the number of jobs.
-
-It may make sense to have a separate AnnexWorkerPool that is used only for
-proxy connections, and where each Annex state is used for a single
-proxied connection. That way, when a proxied connection is evicted from
-the pool, quiesce can be used to stop only it, and not other proxied
-connections.
+It seems ok for the total number of proxy connections across all
+repostories to be capped by the existing limits on the things that
+use them. Basically that is `--jobs + --lockedfiles`. (With clusters,
+`--jobs * --clusterjobs + --lockfiles`)
+
+There needs to be a Map from a proxy remote to Annex state. When opening a
+proxy connection, do it in the Annex state for that particular proxy
+remote. That way, when a proxied connection is evicted from the pool,
+quiesce can be used to stop it, without also stopping other proxied
+connections or other stuff.
+
+---
+
+Update:
+
+In `p2phttpchill` I've done some work toward the above now. Implemented
+the proxy Map, and quiescing and GC.
+
+There seems to be a bug left, where more than --proxyconnections proxies
+can be left running sometimes.
 """]]

markup
diff --git a/doc/git-annex-addcomputed.mdwn b/doc/git-annex-addcomputed.mdwn
index 5a66ed47b5..569544d741 100644
--- a/doc/git-annex-addcomputed.mdwn
+++ b/doc/git-annex-addcomputed.mdwn
@@ -29,11 +29,11 @@ output file. And multiple output files can be computed at the same time.
 
 Some examples of how this might look:
 
-    git-annex addcomputed --to=x -- convert file.raw file.jpeg passes=10
+    `git-annex addcomputed --to=x -- convert file.raw file.jpeg passes=10`
 
-    git-annex addcomputed --to=y -- compress foo --level=9
+    `git-annex addcomputed --to=y -- compress foo --level=9`
 
-    git-annex addcomputed --to=z -- clip foo 2:01-3:00 combine with bar to baz
+    `git-annex addcomputed --to=z -- clip foo 2:01-3:00 combine with bar to baz`
 
 Note that parameters that were passed to `git-annex initremote` 
 when setting up the compute special remote will be appended to the end of
@@ -48,7 +48,7 @@ the parameters provided to `git-annex addcomputed`.
   This must be a compute remote. For example, one can be
   initialized with:
 
-    git-annex initremote photoconv type=compute program=git-annex-compute-photoconv
+    `git-annex initremote photoconv type=compute program=git-annex-compute-photoconv`
 
   For details about compute remotes, and a list of some
   of the programs that are available, see

fix man page formatting
diff --git a/doc/git-annex-addcomputed.mdwn b/doc/git-annex-addcomputed.mdwn
index f6ad7340fc..5a66ed47b5 100644
--- a/doc/git-annex-addcomputed.mdwn
+++ b/doc/git-annex-addcomputed.mdwn
@@ -48,8 +48,7 @@ the parameters provided to `git-annex addcomputed`.
   This must be a compute remote. For example, one can be
   initialized with:
 
-    git-annex initremote photoconv type=compute \
-        program=git-annex-compute-photoconv
+    git-annex initremote photoconv type=compute program=git-annex-compute-photoconv
 
   For details about compute remotes, and a list of some
   of the programs that are available, see

fix example formatting on man page
diff --git a/doc/git-annex-addcomputed.mdwn b/doc/git-annex-addcomputed.mdwn
index 0b59650268..f6ad7340fc 100644
--- a/doc/git-annex-addcomputed.mdwn
+++ b/doc/git-annex-addcomputed.mdwn
@@ -30,7 +30,9 @@ output file. And multiple output files can be computed at the same time.
 Some examples of how this might look:
 
     git-annex addcomputed --to=x -- convert file.raw file.jpeg passes=10
+
     git-annex addcomputed --to=y -- compress foo --level=9
+
     git-annex addcomputed --to=z -- clip foo 2:01-3:00 combine with bar to baz
 
 Note that parameters that were passed to `git-annex initremote` 

update
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_6_ec1228f7a444a0129737124720393c81._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_6_ec1228f7a444a0129737124720393c81._comment
index 9acc31252b..f3c88f34db 100644
--- a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_6_ec1228f7a444a0129737124720393c81._comment
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_6_ec1228f7a444a0129737124720393c81._comment
@@ -3,17 +3,7 @@
  subject="""comment 6"""
  date="2026-11-12T18:59:57Z"
  content="""
-Got this implemented, except for proxied remotes which
-get --proxyconnections connections per repository. Leaving the todo open
-for that.
+Got this implemented, except for proxied remotes. 
 
-It may be that the GC hack is not actually needed to solve that. (Whew!)
-
-The ProxyConnectionPool already supports evicting old connections as
-needed, which will shut down proxied remotes cleanly. Just need to
-make a single ProxyConnectionPool be shared amoung each repository state,
-rather than each having its own pool.
-
-And change the meaning of --proxyconnections to be not maximum
-per repository, but overall. Then it can become a simple quantiry semaphore.
+Leaving the todo open for that.
 """]]
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_7_d481e4106d3bfd4a21d4ed111d1eb03d._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_7_d481e4106d3bfd4a21d4ed111d1eb03d._comment
new file mode 100644
index 0000000000..03bc50e9c3
--- /dev/null
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_7_d481e4106d3bfd4a21d4ed111d1eb03d._comment
@@ -0,0 +1,32 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 7"""
+ date="2026-11-13T11:48:07Z"
+ content="""
+The situation with proxied remotes is:
+
+* --proxyconnections is the number of *idle* connections to cache.
+  It is not a cap on the maximum number. If two requests come in to two
+  different proxied remotes, a connection will be started to both,
+  and once both are done, one of them will remain cached.
+* --proxyconnections is per-repository, so even if it were an upper bound,
+  there could be a lot of processes for proxied external special remotes
+* Evicting a proxied connection from the pool doesn't stop the external
+  special remote process. This is because Remote.list is still populated,
+  and also Remote.External registers a cleanup action that stops it --
+  which is another reference and keeps it running until quiesce.
+  (I have a proof of concept fix for this in `p2phttpchill`.)
+
+I think that --proxyconnections can stay as an idle connections cache
+limit rather than an upper bound on the number of active connections.
+But make it be across all served repos, rather than per-repo.
+
+Add a cap on the number of total proxy connections. It could be the same as
+the number of jobs.
+
+It may make sense to have a separate AnnexWorkerPool that is used only for
+proxy connections, and where each Annex state is used for a single
+proxied connection. That way, when a proxied connection is evicted from
+the pool, quiesce can be used to stop only it, and not other proxied
+connections.
+"""]]

update
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_6_ec1228f7a444a0129737124720393c81._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_6_ec1228f7a444a0129737124720393c81._comment
index c8e0ad3e81..9acc31252b 100644
--- a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_6_ec1228f7a444a0129737124720393c81._comment
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_6_ec1228f7a444a0129737124720393c81._comment
@@ -3,8 +3,9 @@
  subject="""comment 6"""
  date="2026-11-12T18:59:57Z"
  content="""
-The p2phttpchill branch has this implemented, except for proxied remotes which
-get --proxyconnections connections per repository.
+Got this implemented, except for proxied remotes which
+get --proxyconnections connections per repository. Leaving the todo open
+for that.
 
 It may be that the GC hack is not actually needed to solve that. (Whew!)
 

fixed
diff --git a/doc/bugs/proxied_remotes_broken_by_CVE-2026-112818_fix.mdwn b/doc/bugs/proxied_remotes_broken_by_CVE-2026-112818_fix.mdwn
index 6b073a8210..35e44519dc 100644
--- a/doc/bugs/proxied_remotes_broken_by_CVE-2026-112818_fix.mdwn
+++ b/doc/bugs/proxied_remotes_broken_by_CVE-2026-112818_fix.mdwn
@@ -24,3 +24,5 @@ There is actually no security benefit to verification of proxied remotes.
 The remote is accessed through the proxy, whose owner must have already verified
 it themselves, and have the ability to access it. The fix for this bug will 
 almost certianly be to not need verification of these. --[[Joey]]
+
+> [[fixed|done]] with [[!commit ff6fa68f5d211f5096a0daf728d834bf6143b257]] --[[Joey]]
diff --git a/doc/security/cross_repository_data_exfiltration_via_special_remote.mdwn b/doc/security/cross_repository_data_exfiltration_via_special_remote.mdwn
index 37e621ebbf..2c997a9127 100644
--- a/doc/security/cross_repository_data_exfiltration_via_special_remote.mdwn
+++ b/doc/security/cross_repository_data_exfiltration_via_special_remote.mdwn
@@ -64,3 +64,11 @@ After upgrading git-annex, existing special remotes will continue to work
 as before. If a special remote could have been configured by a third party,
 you should verify its configuration using the `git-annex verifyremote`
 command.
+
+----
+
+Note that git-annex 10.20261026 which fixed this (and also backports
+of the security fixes to older versions of git-annex)
+unfortunately broke accessing proxied remotes. See
+[[doc/security/cross_repository_data_exfiltration_via_special_remote]]
+for a workaround if you encounter that reversion.

comment
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_6_ec1228f7a444a0129737124720393c81._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_6_ec1228f7a444a0129737124720393c81._comment
new file mode 100644
index 0000000000..c8e0ad3e81
--- /dev/null
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_6_ec1228f7a444a0129737124720393c81._comment
@@ -0,0 +1,18 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 6"""
+ date="2026-11-12T18:59:57Z"
+ content="""
+The p2phttpchill branch has this implemented, except for proxied remotes which
+get --proxyconnections connections per repository.
+
+It may be that the GC hack is not actually needed to solve that. (Whew!)
+
+The ProxyConnectionPool already supports evicting old connections as
+needed, which will shut down proxied remotes cleanly. Just need to
+make a single ProxyConnectionPool be shared amoung each repository state,
+rather than each having its own pool.
+
+And change the meaning of --proxyconnections to be not maximum
+per repository, but overall. Then it can become a simple quantiry semaphore.
+"""]]

reversion
diff --git a/doc/bugs/proxied_remotes_broken_by_CVE-2026-112818_fix.mdwn b/doc/bugs/proxied_remotes_broken_by_CVE-2026-112818_fix.mdwn
new file mode 100644
index 0000000000..6b073a8210
--- /dev/null
+++ b/doc/bugs/proxied_remotes_broken_by_CVE-2026-112818_fix.mdwn
@@ -0,0 +1,26 @@
+The fix for CVE-2026-112818 broke using proxied special remotes. Existing
+proxied special remotes won't be affected. But when trying to use a new
+one, eg "origin-foo", this happens:
+
+First it prompts for verification:
+
+    copy 2 (This special remote cannot be used since it's not verified. Run this command to verify it: git-annex verifyremote origin-foo) failed
+
+Then, after verification, it no longer thinks it exists!
+
+    git-annex copy --to origin-foo
+    git-annex: there is no available git remote named "origin-foo"
+
+The problem is that verifyremote sets remote.origin-foo.annex-verified,
+but having any config for a remote makes it not be treated as a proxyable
+special remote.
+
+The workaround, if you encounter this bug before it's fixed is
+to just unset that config:
+
+    git config remote.origin-foo.annex-verified false
+
+There is actually no security benefit to verification of proxied remotes.
+The remote is accessed through the proxy, whose owner must have already verified
+it themselves, and have the ability to access it. The fix for this bug will 
+almost certianly be to not need verification of these. --[[Joey]]

comment
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_5_22cef8a22adc9d824d5671c4f1b04827._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_5_22cef8a22adc9d824d5671c4f1b04827._comment
new file mode 100644
index 0000000000..dca2fc5c4c
--- /dev/null
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_5_22cef8a22adc9d824d5671c4f1b04827._comment
@@ -0,0 +1,12 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 5"""
+ date="2026-11-12T15:04:35Z"
+ content="""
+> Question: PerRepoServerState also has an annexState and annexRead that are
+> not part of the AnnexWorkerPool so would not be affected by that.
+> Are those used anywhere that spawns helper processes?
+
+Unfortunately, it appears that they are. A `git cat-file` helper process
+is started outside the AnnexWorkerPool, so there is one per served repository.
+"""]]

formatting
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_4_0cef1dd59e75207a821bfdaa23fde7fc._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_4_0cef1dd59e75207a821bfdaa23fde7fc._comment
index 0947994e54..8d2b2ff2b8 100644
--- a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_4_0cef1dd59e75207a821bfdaa23fde7fc._comment
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_4_0cef1dd59e75207a821bfdaa23fde7fc._comment
@@ -67,7 +67,7 @@ The benefit of #1 is that often the same few repos will get most
 of the traffic. In that case, helper processes get started once
 and used for a long time even when the server is fully loaded.
 
-#1 has an edge case with very bad behavior: A fully loaded server
+But #1 has an edge case with very bad behavior: A fully loaded server
 is serving several requests for frequently used repos and 1 request
 for a little-used repo comes in. That request needs to block
 until all currently active requests for one of the active repos

Fix build when the Memory build flag is disabled
diff --git a/CHANGELOG b/CHANGELOG
index ec816b1ba8..839644fc8c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -18,6 +18,7 @@ git-annex (10.20261105) UNRELEASED; urgency=medium
   * Allow the MAGIC environment variable to override the bundled magic file
     location.
   * NoLLMDependencies: Update for network and http-semantics. 
+  * Fix build when the Memory build flag is disabled.
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/doc/bugs/Building_with___96__ram__96___still_requires___96__memory__96__.mdwn b/doc/bugs/Building_with___96__ram__96___still_requires___96__memory__96__.mdwn
index 51d00e24a1..ebc252d769 100644
--- a/doc/bugs/Building_with___96__ram__96___still_requires___96__memory__96__.mdwn
+++ b/doc/bugs/Building_with___96__ram__96___still_requires___96__memory__96__.mdwn
@@ -51,3 +51,4 @@ Utility/IPAddress.hs:25:1: error:
 ### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
 
 
+> [[fixed|done]]] --[[Joey]]
diff --git a/doc/bugs/Building_with___96__ram__96___still_requires___96__memory__96__/comment_1_69796a44940c07915a45453bed3d6d40._comment b/doc/bugs/Building_with___96__ram__96___still_requires___96__memory__96__/comment_1_69796a44940c07915a45453bed3d6d40._comment
new file mode 100644
index 0000000000..31335fc67c
--- /dev/null
+++ b/doc/bugs/Building_with___96__ram__96___still_requires___96__memory__96__/comment_1_69796a44940c07915a45453bed3d6d40._comment
@@ -0,0 +1,9 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2026-11-10T19:44:10Z"
+ content="""
+The flag is documented as "Build with memory rather than ram",
+so I don't see a problem with git-annex depending on memory as well as ram
+when the flag is not set. So I've made that change.
+"""]]
diff --git a/git-annex.cabal b/git-annex.cabal
index 050b7911f8..78ad113747 100644
--- a/git-annex.cabal
+++ b/git-annex.cabal
@@ -325,7 +325,7 @@ Executable git-annex
   if flag(Memory)
     Build-Depends: memory, crypton (< 1.1.0)
   else
-    Build-Depends: ram, crypton (>= 1.1.0)
+    Build-Depends: memory, ram, crypton (>= 1.1.0)
 
   -- Fully optimize for production.
   if flag(Production)

ugh
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_4_0cef1dd59e75207a821bfdaa23fde7fc._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_4_0cef1dd59e75207a821bfdaa23fde7fc._comment
new file mode 100644
index 0000000000..0947994e54
--- /dev/null
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_4_0cef1dd59e75207a821bfdaa23fde7fc._comment
@@ -0,0 +1,86 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 4"""
+ date="2026-11-10T13:02:49Z"
+ content="""
+This is a harder problem than I thought.
+
+---
+
+There is currently no way to promptly and fully stop all helper processes
+belonging to an AnnexState (or an AnnexRead). Two examples of processes
+that are not stopped by stopCoProcesses are external special remotes and
+external backends.
+
+Similarly, there is no way to close every file handle that might be held
+open by an AnnexState. Eg, a Remote might open a Handle and keep it open.
+
+This could be fixed, but it would be a lot of work and add rarely used code
+paths in multiple Remotes, etc. Which, if they're buggy, shows up as
+p2phttp using more resources than expected for a period of time.
+
+Letting the AnnexState get garbage collected will stop all helper processes
+and close all file handles, but only once the GC runs. If new AnnexStates
+are being spawned faster than they're being forgotten, that could result
+in more helper processes and file handles being allocated than expected.
+So relying on GC doesn't seem feasible, unless the GC is forced to run
+at a particular point (System.Mem.performBlockingMajorGC). Forcing GC
+runs seems dodgy and might hurt performance?
+
+---
+
+When dupState is used to create a pool of AnnexStates that all cooperate
+on one repository, stopCoProcesses on any of them stops the helper 
+processes of all of them. There is no way to only stop 1 of each type of
+helper process. So it's not possible to cool down only one of the
+AnnexStates that has not been recently used, while leaving the rest warmed
+up. This is particularly a problem for the cat-file process, which is
+shared amoung all AnnexStates in the pool, and so can't possibly be stopped
+without cooling down the whole pool. (It might be solvable for the other
+helper processes.)
+
+If dupState is not used and AnnexStates are fully independent,
+then stopCoProcesses can be used to cool one down, but then each time one
+is warmed up it has to restart everything.
+
+---
+
+Here are two approaches that avoid the problems of stopCoProcesses:
+
+1. Bounded pool of PerRepoServerStates. When starting a new one,
+   cool down all the AnnexStates in the pool for the LRU one.
+   (Which stopCoProcesses will do just fine.)
+2. No dupState, so all AnnexStates are fully independent.
+   Before using an AnnexState, if there are too many warmed up,
+   find the LRU one and cool it down.
+
+With #1, there would need to be another option, like
+--max-concurrent-repos. The maximum number of running helper
+processes would be on the order of that times --jobs.
+(Though it can be much less, eg when all traffic is going to a few
+repositories and other repositories are not being used.)
+
+With #2, the total number of running helper processes
+would be on the order of --jobs.
+
+The benefit of #1 is that often the same few repos will get most
+of the traffic. In that case, helper processes get started once
+and used for a long time even when the server is fully loaded.
+
+#1 has an edge case with very bad behavior: A fully loaded server
+is serving several requests for frequently used repos and 1 request
+for a little-used repo comes in. That request needs to block
+until all currently active requests for one of the active repos
+are complete. And for fairness, while that request is blocked,
+no new requests can be started that would keep an active repo active.
+And, once that request is running, new requests for the now inactive
+repo also have to block.
+
+With #2, a fully loaded server will frequently have the 
+LRU AnnexState getting cooled down, and then soon after warmed up
+and needing to restart everything. That will increase CPU load when the
+server is fully loaded. But it won't cause any long-term stalls,
+requests will always be handled in the order received.
+
+So #2 seems better.
+"""]]

diff --git a/doc/bugs/Building_with___96__ram__96___still_requires___96__memory__96__.mdwn b/doc/bugs/Building_with___96__ram__96___still_requires___96__memory__96__.mdwn
new file mode 100644
index 0000000000..51d00e24a1
--- /dev/null
+++ b/doc/bugs/Building_with___96__ram__96___still_requires___96__memory__96__.mdwn
@@ -0,0 +1,53 @@
+### Please describe the problem in your own words.
+git-annex fails to build with -f-memory because git-annex uses memory even when crypton does not.
+
+### What steps will reproduce the problem?
+Build `git-annex` using -f-memory.
+
+### What version of git-annex are you using? On what operating system?
+* OS: Arch Linux
+* Kernel: Linux 7.2.14-arch1-1
+* GHC: 9.6.7
+* git-annex: 10.20261026
+* crypton: 1.1.0
+* ram: 0.22.1
+* memory: 0.18.0
+
+### Please provide any additional information below.
+
+Two files explicitly import from memory. They are used internally by git-annex and are acknowledged in cac57ad1fef58a4d50fcc9818b5e86edaf1a7cd2.
+
+The introduction of the Memory flag in 51de0b3a617a77c6fe8a5f4b5c9b531eabd42bb0 removes the memory dependency when using crypton 1.1.0. This leads to the observed build failure.
+
+The obvious fix would be to add memory as a dependency even with -f-memory; the resulting inconsistency might require a renaming of the flag.
+
+[[!format sh """
+[ 38 of 763] Compiling Utility.Hash.Types ( Utility/Hash/Types.hs, dist/build/git-annex/git-annex-tmp/Utility/Hash/Types.dyn_o )
+
+Utility/Hash/Types.hs:14:1: error:
+    Could not find module ‘Data.ByteArray.Encoding’
+    Perhaps you meant
+      Data.ByteArray.Encoding (needs flag -package-id memory-0.18.0)
+      Data.ByteArray.Encoding (from ram-0.22.1)
+      Data.ByteArray.Mapping (needs flag -package-id memory-0.18.0)
+   |
+14 | import qualified "memory" Data.ByteArray.Encoding as BAE
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+[...]
+[ 83 of 763] Compiling Utility.IPAddress ( Utility/IPAddress.hs, dist/build/git-annex/git-annex-tmp/Utility/IPAddress.dyn_o )
+
+Utility/IPAddress.hs:25:1: error:
+    Could not find module ‘Data.Memory.Endian’
+    Perhaps you meant
+      Data.Memory.Endian (needs flag -package-id memory-0.18.0)
+      Data.Memory.Endian (from ram-0.22.1)
+   |
+25 | import "memory" Data.Memory.Endian
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+# End of transcript or log.
+"""]]
+
+### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
+
+

comment
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_3_02008c8b32d39ab5e6d6d893f57311b6._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_3_02008c8b32d39ab5e6d6d893f57311b6._comment
new file mode 100644
index 0000000000..e311d24c22
--- /dev/null
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_3_02008c8b32d39ab5e6d6d893f57311b6._comment
@@ -0,0 +1,7 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 3"""
+ date="2026-11-09T20:36:05Z"
+ content="""
+Started work on this in the `p2phttpchill` branch.
+"""]]

comment
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_2_7fa3ebf75fcbb727d5620e1971e9c82e._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_2_7fa3ebf75fcbb727d5620e1971e9c82e._comment
index 610970f34e..4bb337a726 100644
--- a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_2_7fa3ebf75fcbb727d5620e1971e9c82e._comment
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_2_7fa3ebf75fcbb727d5620e1971e9c82e._comment
@@ -3,13 +3,15 @@
  subject="""comment 2"""
  date="2026-11-08T23:48:18Z"
  content="""
-Refinement: The AnnexWorkerPool is already a bounded pool of Annex objects,
-so lift it out of PerRepoServerState so there is a single bounded pool.
+Refinement: Each PerRepoServerState already has a WorkerPool of Annex
+objects.
 
-This would let --jobs control the total number of worker threads across all
-repositories served. An especially active repository would get more worker
-threads actively running. And helper processes, started by the worker
-threads, would be limited to a small multiple of --jobs.
+When returning an Annex object to the pool, decide if it should be left
+warmed up, or be cooled down (stopping helper processes etc).
+
+To decide, have a quantity semaphore. Check it when getting an Annex
+object from the WorkerPool. If there are too many warm objects, block
+until one gets cooled down.
 
 Question: PerRepoServerState also has an annexState and annexRead that are
 not part of the AnnexWorkerPool so would not be affected by that. 

idea
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_2_7fa3ebf75fcbb727d5620e1971e9c82e._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_2_7fa3ebf75fcbb727d5620e1971e9c82e._comment
new file mode 100644
index 0000000000..610970f34e
--- /dev/null
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_2_7fa3ebf75fcbb727d5620e1971e9c82e._comment
@@ -0,0 +1,17 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 2"""
+ date="2026-11-08T23:48:18Z"
+ content="""
+Refinement: The AnnexWorkerPool is already a bounded pool of Annex objects,
+so lift it out of PerRepoServerState so there is a single bounded pool.
+
+This would let --jobs control the total number of worker threads across all
+repositories served. An especially active repository would get more worker
+threads actively running. And helper processes, started by the worker
+threads, would be limited to a small multiple of --jobs.
+
+Question: PerRepoServerState also has an annexState and annexRead that are
+not part of the AnnexWorkerPool so would not be affected by that. 
+Are those used anywhere that spawns helper processes?
+"""]]

closing
diff --git a/doc/bugs/incorrectly_constructs_path_causing_git_status_err.mdwn b/doc/bugs/incorrectly_constructs_path_causing_git_status_err.mdwn
index 8b4cb38448..bd1e631621 100644
--- a/doc/bugs/incorrectly_constructs_path_causing_git_status_err.mdwn
+++ b/doc/bugs/incorrectly_constructs_path_causing_git_status_err.mdwn
@@ -45,3 +45,5 @@ operating within worktrees would soon be more popular within repronim circles as
 
 [[!meta author=yoh]]
 [[!tag projects/repronim]]
+
+[[done]] as a duplicate and already addressed 

Added a comment
diff --git a/doc/bugs/incorrectly_constructs_path_causing_git_status_err/comment_2_27bcc104a89d987d51c172bbb094dad9._comment b/doc/bugs/incorrectly_constructs_path_causing_git_status_err/comment_2_27bcc104a89d987d51c172bbb094dad9._comment
new file mode 100644
index 0000000000..4533c92263
--- /dev/null
+++ b/doc/bugs/incorrectly_constructs_path_causing_git_status_err/comment_2_27bcc104a89d987d51c172bbb094dad9._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="yarikoptic"
+ avatar="http://cdn.libravatar.org/avatar/f11e9c84cb18d26a1748c33b48c924b4"
+ subject="comment 2"
+ date="2026-11-06T19:39:25Z"
+ content="""
+d'oh -- right! installed 10.20261026+git114-ge7b75e58eb-1~ndall+1 form con/datalad builds and it works now. Thanks!
+"""]]

comment
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_1_ea9e4a5a452f4e001816403aac749d55._comment b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_1_ea9e4a5a452f4e001816403aac749d55._comment
new file mode 100644
index 0000000000..c2156feae5
--- /dev/null
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes/comment_1_ea9e4a5a452f4e001816403aac749d55._comment
@@ -0,0 +1,27 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2026-11-06T18:11:13Z"
+ content="""
+Another approach would be to keep a bounded pool of PerRepoServerStates.
+When a PerRepoServerState needs to be added to the pool and the
+pool is full, first find one for a different repository that is not in use,
+and remove it from the pool, stopping its helper processes.
+
+This seems easier to implement than coordinating a process pool 
+between Annex objects.
+
+And it could also avoid problems with too many open files, since it
+could also close open file handles in the Annex object such as the
+keys db handle. I do think that running out of file descriptors would be
+the next problem after too many processes when scaling p2phttp up to
+serving a large number of repositories, so this might as well solve that
+too.
+
+There would still be worker pools for processes like `git hash-object`
+that are shared amoung all threads serving the same repository,
+scaling up to one per cpu. So long as the pool is capped at some reasonable
+number (say 100), that is ok, it means something like `100 * (cpus + 1)`
+processes, probably well under the maximum number of processes. Make
+the maximum concurrent repositories served a tunable value.
+"""]]

todo
diff --git a/doc/todo/avoid_p2phttp_having_too_many_helper_processes.mdwn b/doc/todo/avoid_p2phttp_having_too_many_helper_processes.mdwn
new file mode 100644
index 0000000000..f6a1452564
--- /dev/null
+++ b/doc/todo/avoid_p2phttp_having_too_many_helper_processes.mdwn
@@ -0,0 +1,31 @@
+When `git-annex p2phttp` is serving many git repositories at the same time,
+it should avoid running a large number of helper processes like
+`git cat-file` and `git hash-object`. To avoid running out of process
+limits.
+
+Currently for N repositories, there are N cat-files,
+but `(min jobs cpus) * N` hash-objects. (And the same for check-attr and
+check-ignore, although I don't think p2phttp would usually run those.)
+
+The point of that parallelization is that hashing is a cpu bound operation
+and so can parallelize. But that was implemented assuming git-annex is
+running in 1 repository, while with p2phttp it can be running in multiple
+repositories. (With a large number of local git remotes it might also be
+possible for the same kind of problem to happen.)
+
+Even the 1 cat-file per repository can be too many, if there are thousands
+of repositories, eventually there will be thousands of cat-files, most of
+them long idle, and it will eventually run out of process limits.
+
+Seems to me that this needs a pool that is in some way shared amoung all the
+repositories, and is capped out at some reasonable maximum number of
+processes. So when a new cat-file or hash-object is needed and the pool is
+full, it can find a currently unused process running in some other 
+repository and free it.
+
+The reasonable maximum number is more than the number of cpus, because
+stopping and later restarting one of these processes is not a free
+operation. Some fraction of the maximum number of processes might be a good
+value. --[[Joey]]
+
+[[!tag projects/ICE4]]

reword
diff --git a/doc/bugs/incorrectly_constructs_path_causing_git_status_err/comment_1_8b1f417485a5e6c2c5601f25b71e66ca._comment b/doc/bugs/incorrectly_constructs_path_causing_git_status_err/comment_1_8b1f417485a5e6c2c5601f25b71e66ca._comment
index 1870fa101c..24544ce1b8 100644
--- a/doc/bugs/incorrectly_constructs_path_causing_git_status_err/comment_1_8b1f417485a5e6c2c5601f25b71e66ca._comment
+++ b/doc/bugs/incorrectly_constructs_path_causing_git_status_err/comment_1_8b1f417485a5e6c2c5601f25b71e66ca._comment
@@ -3,6 +3,6 @@
  subject="""comment 1"""
  date="2026-11-06T14:32:36Z"
  content="""
-Isn't this the same problem as
-[[info_fails_to_resolve_from_submodule_in_worktree]]?
+I think this is the problem as the (already fixed)
+[[info_fails_to_resolve_from_submodule_in_worktree]].
 """]]

dup
diff --git a/doc/bugs/incorrectly_constructs_path_causing_git_status_err/comment_1_8b1f417485a5e6c2c5601f25b71e66ca._comment b/doc/bugs/incorrectly_constructs_path_causing_git_status_err/comment_1_8b1f417485a5e6c2c5601f25b71e66ca._comment
new file mode 100644
index 0000000000..1870fa101c
--- /dev/null
+++ b/doc/bugs/incorrectly_constructs_path_causing_git_status_err/comment_1_8b1f417485a5e6c2c5601f25b71e66ca._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2026-11-06T14:32:36Z"
+ content="""
+Isn't this the same problem as
+[[info_fails_to_resolve_from_submodule_in_worktree]]?
+"""]]

comment
diff --git a/doc/todo/provide_more_info_on___34__..._not_available_in_S3_...__34__/comment_1_d672d81d0edebb1ecd9572825745d8a8._comment b/doc/todo/provide_more_info_on___34__..._not_available_in_S3_...__34__/comment_1_d672d81d0edebb1ecd9572825745d8a8._comment
new file mode 100644
index 0000000000..b76287a45e
--- /dev/null
+++ b/doc/todo/provide_more_info_on___34__..._not_available_in_S3_...__34__/comment_1_d672d81d0edebb1ecd9572825745d8a8._comment
@@ -0,0 +1,60 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2026-11-06T13:30:05Z"
+ content="""
+This error message is displayed when S3 getObject fails with a 412
+"Precondition Failed".
+
+I suspect you may be running into S3 eventual consistency issues.
+
+The key is of course the one pointed to by the file that it's acting on
+when this error is displayed. So I don't see any reason for git-annex to
+display the key.
+
+As for displaying the versionid or etag, I think that would already be done
+by --debug when it shows the http request headers.
+
+I did develop this patch to display that as part of the error message,
+but I'd only be inclined to apply it if --debug doesn't have the
+information.
+
+	diff --git a/Remote/S3.hs b/Remote/S3.hs
+	index bf77e56419..74569a0a3a 100644
+	--- a/Remote/S3.hs
+	+++ b/Remote/S3.hs
+	@@ -762,9 +762,10 @@ retrieveExportWithContentIdentifierS3 hv r rs info loc (cid:_) dest gk p =
+	   where
+	 	go iv = withS3Handle hv $ \case
+	 		Right h -> do
+	-			rewritePreconditionException $ retrieveHelper' (gitconfig r) info h dest p iv $
+	-				limitGetToContentIdentifier cid $
+	-					S3.getObject (bucket info) o
+	+			rewritePreconditionException cid o $
+	+				retrieveHelper' (gitconfig r) info h dest p iv $
+	+					limitGetToContentIdentifier cid $
+	+						S3.getObject (bucket info) o
+	 			k <- either return id gk
+	 			case extractContentIdentifier cid o of
+	 				Right vid -> do
+	@@ -779,12 +780,16 @@ retrieveExportWithContentIdentifierS3 _ _ _ _ _ [] _ _ _ = giveup "missing conte
+	 
+	 {- Catch exception getObject returns when a precondition is not met,
+	  - and replace with a more understandable message for the user. -}
+	-rewritePreconditionException :: Annex a -> Annex a
+	-rewritePreconditionException a = catchJust (Url.matchStatusCodeException want) a $ 
+	-	const $ giveup "requested version of object is not available in S3 bucket"
+	+rewritePreconditionException :: ContentIdentifier -> S3.Object -> Annex a -> Annex a
+	+rewritePreconditionException cid o a = catchJust (Url.matchStatusCodeException want) a $ 
+	+	const $ giveup $ "requested version of object (" ++ ciddesc ++ ") ++ is not available in S3 bucket"
+	   where
+	 	want st = statusCode st == 412 && 
+	 		statusMessage st == "Precondition Failed"
+	+	ciddesc = case extractContentIdentifier cid o of
+	+		Left etag -> "etag " ++ T.unpack etag
+	+		Right (Just (S3VersionID _ vid)) -> T.unpack vid
+	+		Right Nothing -> mempty
+	 
+	 -- Does not check if content on S3 is safe to overwrite, because there
+	 -- is no atomic way to do so. When the bucket is versioned, this is
+"""]]

Added a comment: macOS builds
diff --git a/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_5_191fbecc6ef032e9ec2ec0effadb8b3e._comment b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_5_191fbecc6ef032e9ec2ec0effadb8b3e._comment
new file mode 100644
index 0000000000..5317d3eac6
--- /dev/null
+++ b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_5_191fbecc6ef032e9ec2ec0effadb8b3e._comment
@@ -0,0 +1,20 @@
+[[!comment format=mdwn
+ username="ewen"
+ avatar="http://cdn.libravatar.org/avatar/605b2981cb52b4af268455dee7a4f64e"
+ subject="macOS builds"
+ date="2026-11-06T03:51:06Z"
+ content="""
+Thanks for the pointer to the CHANGELOG.  I'd missed that file once I'd cloned the repo.  FWIW the CHANGELOG (a) doesn't seem to be linked anywhere obvious for web viewing, (b) the gitweb view is pretty hidden (only linked on the [download](https://git-annex.branchable.com/download/) page, which is possibly only linked from the [install/fromsource](https://git-annex.branchable.com/install/fromsource/) page), and (c) the gitweb is quite slow.  So at present cloning the git repo is probably the easiest way to to read the CHANGELOG (versus doc/news which is on the web site, but only a recent subset).
+
+Possibly there's some way to make the `CHANGELOG` file available as a plain text view on the web more directly than \"find the gitweb, navigate to the tree\"?  And maybe link it from a doc/news article that sorts just after the \"recent changes\", as a \"for older release notes, see...\".
+
+For the record, from some digging my guess is that the macOS 10.15 to macOS 14/15 minimum version change happened when the builds moved to GitHub Actions (and thus GitHub Runners), which as best I can tell was around May 2026.  So builds up to April 2026 *might* also work on macOS 10.15.  But I didn't see anything particularly relevant to my use case between January 2026 and April 2026, so I've not tried any builds newer than December 2025 on the macOS 10.15 system.
+
+I'd tend to agree with just having a single macOS build at this point.  The next likely incompatible change is to stop building Intel/macOS \"fat\" binaries, which I suspect some combination of Apple and/or GitHub will force on you sometime in the next 12 months.  But that's a very obvious failure case -- you get \"Bad CPU type in executable\" when trying to run anything if running an ARM-only build on an Intel system.
+
+So it'd be worth documenting the expected minimum supported macOS for the standalone build (here macOS 15, or maybe macOS 14), but it doesn't need to be in the directory name.  And it'd be worth noting when the builds go ARM/M1-only (which I'd guess will be when GitHub remove the macOS Intel runners, likely in 2027, or when macOS 15 goes unsupported).
+
+If you haven't already, I think you can mark this issue as \"done\" at your convenience, as I have setup that seems to work now, and the directory name is no longer misleading.
+
+Ewen
+"""]]

Added a comment
diff --git a/doc/todo/recover_from_export_of_corrupted_object/comment_10_aa1d2b8b2e5f6671575efb81f0ba9df6._comment b/doc/todo/recover_from_export_of_corrupted_object/comment_10_aa1d2b8b2e5f6671575efb81f0ba9df6._comment
new file mode 100644
index 0000000000..febdbd4ea1
--- /dev/null
+++ b/doc/todo/recover_from_export_of_corrupted_object/comment_10_aa1d2b8b2e5f6671575efb81f0ba9df6._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="yarikoptic"
+ avatar="http://cdn.libravatar.org/avatar/f11e9c84cb18d26a1748c33b48c924b4"
+ subject="comment 10"
+ date="2026-11-05T21:09:22Z"
+ content="""
+FTR: fix was in [10.20260115-40-g2d1296768a AKA 10.20260213~65](https://git.kitenet.net/index.cgi/git-annex.git/commit/?id=2d1296768aea71ae86a91ae581bcd343f1f098a0)
+"""]]

Added a comment
diff --git a/doc/todo/recover_from_export_of_corrupted_object/comment_9_b46fe73353f0340c545009de1ab9b2c5._comment b/doc/todo/recover_from_export_of_corrupted_object/comment_9_b46fe73353f0340c545009de1ab9b2c5._comment
new file mode 100644
index 0000000000..a8ef36a94c
--- /dev/null
+++ b/doc/todo/recover_from_export_of_corrupted_object/comment_9_b46fe73353f0340c545009de1ab9b2c5._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="yarikoptic"
+ avatar="http://cdn.libravatar.org/avatar/f11e9c84cb18d26a1748c33b48c924b4"
+ subject="comment 9"
+ date="2026-11-05T21:08:36Z"
+ content="""
+FTR: fix was in [10.20260115-40-g2d1296768a AKA 10.20260213~65](https://git.kitenet.net/index.cgi/git-annex.git/commit/?id=2d1296768aea71ae86a91ae581bcd343f1f098a0)
+"""]]

initial report on git status err
diff --git a/doc/bugs/incorrectly_constructs_path_causing_git_status_err.mdwn b/doc/bugs/incorrectly_constructs_path_causing_git_status_err.mdwn
new file mode 100644
index 0000000000..8b4cb38448
--- /dev/null
+++ b/doc/bugs/incorrectly_constructs_path_causing_git_status_err.mdwn
@@ -0,0 +1,47 @@
+### Please describe the problem in your own words.
+
+I am in a worktree checkout within `.git-meta/worktrees/` subtree, and then when running `git status` git annex crashes due to building up the path having `.git-meta/.git-meta/worktrees/`:
+
+
+```shell
+❯ pwd
+/home/yoh/proj/CON/talks/.git-meta/worktrees/enh-old-talks
+
+❯ git status
+Refresh index: 100% (826/826), done.
+git-annex: /home/yoh/proj/CON/talks/.git-meta/.git-meta/worktrees/enh-old-talks/sourcedata/con-site-specific: changeWorkingDirectory: does not exist (No such file or directory)
+HasCallStack backtrace:
+  collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:169:13 in ghc-internal:GHC.Internal.Exception
+  toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
+  throwIO, called at libraries/exceptions/src/Control/Monad/Catch.hs:308:12 in exceptions-0.10.9-d4a4:Control.Monad.Catch
+  throwM, called at Messages.hs:387:17 in git-annex-10.20260901-63197oCsGBZEJZmwc2PKTY-git-annex:Messages
+
+
+fatal: the remote end hung up unexpectedly
+fatal: 'git status --porcelain=2' failed in submodule sourcedata/con-site-specific
+
+❯ ls -ld .git
+lrwxrwxrwx 1 yoh yoh 37 Nov  5 13:23 .git -> ../../../.git/worktrees/enh-old-talks/
+
+❯ ls -ld /home/yoh/proj/CON/talks/.git-meta/.git-meta/worktrees/enh-old-talks/sourcedata/con-site-specific /home/yoh/proj/CON/talks/.git-meta/worktrees/enh-old-talks/sourcedata/con-site-specific
+ls: cannot access '/home/yoh/proj/CON/talks/.git-meta/.git-meta/worktrees/enh-old-talks/sourcedata/con-site-specific': No such file or directory
+drwxr-xr-x 1 yoh yoh 366 Nov  5 13:48 /home/yoh/proj/CON/talks/.git-meta/worktrees/enh-old-talks/sourcedata/con-site-specific/
+
+```
+
+FWIW the repo itself is https://datasets.datalad.org/centerforopenneuroscience/talks/.git .
+
+
+
+### What version of git-annex are you using? On what operating system?
+
+```shell
+❯ git annex version | head -n 1
+git-annex version: 10.20260901-g29d2c4f5723a6bc5dc380b5fd75b4e60aad6e4e1
+
+```
+
+operating within worktrees would soon be more popular within repronim circles as `babs` got that functionality, so we better make it right
+
+[[!meta author=yoh]]
+[[!tag projects/repronim]]

network LLM code :-(
diff --git a/CHANGELOG b/CHANGELOG
index d5d67b85b7..ec816b1ba8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -17,7 +17,7 @@ git-annex (10.20261105) UNRELEASED; urgency=medium
     (Reversion from 10.20261005)
   * Allow the MAGIC environment variable to override the bundled magic file
     location.
-  * NoLLMDependencies: Update for http-semantics. 
+  * NoLLMDependencies: Update for network and http-semantics. 
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/doc/no_llm_code.mdwn b/doc/no_llm_code.mdwn
index 5c1fda18cd..a5bc71f34e 100644
--- a/doc/no_llm_code.mdwn
+++ b/doc/no_llm_code.mdwn
@@ -132,6 +132,12 @@ for https, and for the skein and blake2 hashes.
 
 [First LLM generated code](https://github.com/haskell-tls/hs-tls/commit/218abdd67a4cde30854b5ca9b146b269da53bc07)
 
+### network
+
+[network](https://hackage.haskell.org/package/network) since 3.2.9.0
+
+[First LLM generated code](https://github.com/haskell/network/commit/c4f7be5a9d726128b8377c0144d5c4ef840b76ca)
+
 ### http-semantics
 
 [http-semantics](https://hackage.haskell.org/package/http-semantics) since
diff --git a/git-annex.cabal b/git-annex.cabal
index 9f9e1d54ff..554eee4c79 100644
--- a/git-annex.cabal
+++ b/git-annex.cabal
@@ -309,6 +309,7 @@ Executable git-annex
     Build-Depends:
      base (>= 4.18.2.1 && < 4.23),
      ram (< 0.21.0),
+     network (< 3.2.9.0),
      persistent (>= 2.13.3) && (< 2.15.0.0),
      warp (< 3.4.11),
      http-semantics (< 0.4.2),

reorg
diff --git a/doc/no_llm_code.mdwn b/doc/no_llm_code.mdwn
index 137660d0df..5c1fda18cd 100644
--- a/doc/no_llm_code.mdwn
+++ b/doc/no_llm_code.mdwn
@@ -48,8 +48,6 @@ bug reports on git-annex if it cannot be built without LLM generated code.
 [This commit](https://github.com/ghc/ghc/commit/a5ec467ee3d4e77c026437a545981269acde3434)
 is probably the first, and will be released in the upcoming ghc 9.15.
 
-[GHC policy on LLM contributions](https://gitlab.haskell.org/ghc/ghc/-/wikis/contributing/AI)
-
 git-annex remains buildable with older versions of ghc back to 9.6.6.
 
 This will probably prevent git-annex from taking advantage of 
@@ -59,6 +57,8 @@ guaranteed to never change to depend on LLM generated code, because cutting
 it off from all future Haskell language improvements may be worse than the
 alternative.
 
+[GHC policy on LLM contributions](https://gitlab.haskell.org/ghc/ghc/-/wikis/contributing/AI)
+
 ### ram
 
 [ram](https://hackage.haskell.org/package/ram) since 0.21.0.

updates
diff --git a/doc/no_llm_code.mdwn b/doc/no_llm_code.mdwn
index c4ab36a3dc..137660d0df 100644
--- a/doc/no_llm_code.mdwn
+++ b/doc/no_llm_code.mdwn
@@ -48,6 +48,8 @@ bug reports on git-annex if it cannot be built without LLM generated code.
 [This commit](https://github.com/ghc/ghc/commit/a5ec467ee3d4e77c026437a545981269acde3434)
 is probably the first, and will be released in the upcoming ghc 9.15.
 
+[GHC policy on LLM contributions](https://gitlab.haskell.org/ghc/ghc/-/wikis/contributing/AI)
+
 git-annex remains buildable with older versions of ghc back to 9.6.6.
 
 This will probably prevent git-annex from taking advantage of 
@@ -122,6 +124,8 @@ that, and as such, does not seem like a trustworthly package to use.
 git-annex has switched for botan for hashing, but still depends on crypton
 for https, and for the skein and blake2 hashes.
 
+(See [[todo/remove_dependency_on_crypon_and_tls]])
+
 ### tls
 
 [tls](https://hackage.haskell.org/package/tls) since 2.4.4

update
diff --git a/doc/no_llm_code.mdwn b/doc/no_llm_code.mdwn
index 78492f23a5..c4ab36a3dc 100644
--- a/doc/no_llm_code.mdwn
+++ b/doc/no_llm_code.mdwn
@@ -81,8 +81,6 @@ in order to prevent such dependencies using the newer version.
 
 [First LLM generated code](https://github.com/yesodweb/persistent/commit/ac0a8698f38ae3b07acdacf0ce236d7fc73bb077)
 
-git-annex supports being built with older versions.
-
 ### warp
 
 [warp](https://hackage.haskell.org/package/warp) since 3.4.11
@@ -121,12 +119,8 @@ The point of cryptonite was to package up industry standard C
 implementations of crypto functions in haskell. This is no longer
 that, and as such, does not seem like a trustworthly package to use.
 
-git-annex supports being built with older versions. The botan build flag
-makes git-annex use botan rather than crypton for hashing (except for skein
-and blake2 hashes).
-
-Unfortunately, crypton is a dependency of several other packages,
-including `tls`. 
+git-annex has switched for botan for hashing, but still depends on crypton
+for https, and for the skein and blake2 hashes.
 
 ### tls
 
@@ -134,8 +128,6 @@ including `tls`.
 
 [First LLM generated code](https://github.com/haskell-tls/hs-tls/commit/218abdd67a4cde30854b5ca9b146b269da53bc07)
 
-git-annex supports being built with older versions.
-
 ### http-semantics
 
 [http-semantics](https://hackage.haskell.org/package/http-semantics) since
@@ -145,15 +137,13 @@ git-annex supports being built with older versions.
 
 An indirect dependency of warp.
 
-git-annex supports being built with older versions.
-
 ### Cabal
 
 [First LLM generated code](https://github.com/haskell/cabal/commit/da8b314563feb15a3df7bc1baeef4b7aa08f7578)
 
 Cabal is needed to build git-annex, but is not linked into it.
 There is a risk that a new version of Cabal could need changes to
-git-annex.cabal that prevent an old version building it.
+git-annex.cabal that prevents an old version building it.
 
 ### git
 

NoLLMDependencies: Update for http-semantics
diff --git a/CHANGELOG b/CHANGELOG
index 178a236c0f..d5d67b85b7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -17,6 +17,7 @@ git-annex (10.20261105) UNRELEASED; urgency=medium
     (Reversion from 10.20261005)
   * Allow the MAGIC environment variable to override the bundled magic file
     location.
+  * NoLLMDependencies: Update for http-semantics. 
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/doc/no_llm_code.mdwn b/doc/no_llm_code.mdwn
index d24c258839..78492f23a5 100644
--- a/doc/no_llm_code.mdwn
+++ b/doc/no_llm_code.mdwn
@@ -138,7 +138,14 @@ git-annex supports being built with older versions.
 
 ### http-semantics
 
-TODO
+[http-semantics](https://hackage.haskell.org/package/http-semantics) since
+0.4.2
+
+[First LLM generated code](https://github.com/kazu-yamamoto/http-semantics/commit/278a0d1ddaba9b52f3ba72cc3449d06839dca3cc)
+
+An indirect dependency of warp.
+
+git-annex supports being built with older versions.
 
 ### Cabal
 
diff --git a/git-annex.cabal b/git-annex.cabal
index db773291b6..9f9e1d54ff 100644
--- a/git-annex.cabal
+++ b/git-annex.cabal
@@ -311,6 +311,7 @@ Executable git-annex
      ram (< 0.21.0),
      persistent (>= 2.13.3) && (< 2.15.0.0),
      warp (< 3.4.11),
+     http-semantics (< 0.4.2),
      crypton (< 1.1.0),
      tls (< 2.4.4)
   else

Merge branch 'botan-default'
Fix testremote failure with p2phttp remote
The reprobe for the remote's annex.url config made the a remote that had
been made unavailable be available again.
The cleanest fix seems to be to avoid doing the reprobe when the url is set
to the special value used for unavailable. I don't entirely like checking
explicitly for that value or for a remote being unavailable, but the way
the reprobe works makes it hard to fix this in Remote.Git.
diff --git a/CHANGELOG b/CHANGELOG
index 7b0074d774..484f4ddcb8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -12,6 +12,8 @@ git-annex (10.20261027) UNRELEASED; urgency=medium
   * Added transfers command, displays a live view of transfers.
   * Avoid display issues with multiple progress meters when not in a
     unicode locale.
+  * Fix testremote failure with p2phttp remote.
+    (Reversion from 10.20261005)
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/P2P/Http/Client.hs b/P2P/Http/Client.hs
index 0182c81d0e..ab52d0fd2b 100644
--- a/P2P/Http/Client.hs
+++ b/P2P/Http/Client.hs
@@ -91,15 +91,17 @@ p2pHttpClientVersions allowedversion rmt reprobeurl fallback clientaction = do
 	-- When unable to speak to the server, re-probe for its url, and
 	-- try again if it changed. This avoids the user needing to manually
 	-- update the remote.name.annexUrl config.
-	fallbackreprobe s = do
-		r <- reprobeurl
-		gc <- Annex.getRemoteGitConfig r
-		case remoteAnnexP2PHttpUrl gc of
-			Just p2purl' | Just p2purl' /= p2purl ->
-				p2pHttpClientVersions' allowedversion p2purl' rmt r fallback clientaction >>= \case
-					Just res -> return res
-					Nothing -> fallback s
-			_ -> fallback s
+	fallbackreprobe s
+		| maybe False isUnavailableP2PHttpUrl p2purl = fallback s
+		| otherwise = do
+			r <- reprobeurl
+			gc <- Annex.getRemoteGitConfig r
+			case remoteAnnexP2PHttpUrl gc of
+				Just p2purl' | Just p2purl' /= p2purl ->
+					p2pHttpClientVersions' allowedversion p2purl' rmt r fallback clientaction >>= \case
+						Just res -> return res
+						Nothing -> fallback s
+				_ -> fallback s
 
 p2pHttpClientVersions'
 	:: (ProtocolVersion -> Bool)
diff --git a/P2P/Http/Url.hs b/P2P/Http/Url.hs
index d035628763..f614744c27 100644
--- a/P2P/Http/Url.hs
+++ b/P2P/Http/Url.hs
@@ -70,6 +70,9 @@ unavailableP2PHttpUrl :: P2PHttpUrl -> P2PHttpUrl
 unavailableP2PHttpUrl p = p
 	{ p2pHttpBaseUrl = (p2pHttpBaseUrl p) { baseUrlHost = "!dne!" } }
 
+isUnavailableP2PHttpUrl :: P2PHttpUrl -> Bool
+isUnavailableP2PHttpUrl p = unavailableP2PHttpUrl p == p
+
 -- When a p2phttp url is on the same host as a git repo, which also uses
 -- http, the same username+password is assumed to be used for both.
 isP2PHttpSameHost :: P2PHttpUrl -> Git.Repo -> Bool
diff --git a/doc/bugs/testremote_fails_with_p2phttp_remote.mdwn b/doc/bugs/testremote_fails_with_p2phttp_remote.mdwn
index 30d61e861b..6986023e14 100644
--- a/doc/bugs/testremote_fails_with_p2phttp_remote.mdwn
+++ b/doc/bugs/testremote_fails_with_p2phttp_remote.mdwn
@@ -158,3 +158,5 @@ To me it looks like it is not replacing the remote url with a proper unavailable
 
 
 [[!tag projects/ICE4]]
+
+> [[fixed|done]] --[[Joey]]
diff --git a/doc/bugs/testremote_fails_with_p2phttp_remote/comment_1_02958ee913c13b30fcfeaab6054155f5._comment b/doc/bugs/testremote_fails_with_p2phttp_remote/comment_1_02958ee913c13b30fcfeaab6054155f5._comment
index b1d9b57b8b..ffd914ddac 100644
--- a/doc/bugs/testremote_fails_with_p2phttp_remote/comment_1_02958ee913c13b30fcfeaab6054155f5._comment
+++ b/doc/bugs/testremote_fails_with_p2phttp_remote/comment_1_02958ee913c13b30fcfeaab6054155f5._comment
@@ -3,6 +3,6 @@
  subject="""comment 1"""
  date="2026-11-04T16:28:52Z"
  content="""
-bisected to [[!f0c046f016aab6485eea57101df953895728e8b4]],
+bisected to [[!commit f0c046f016aab6485eea57101df953895728e8b4]],
 "re-probing of annex.url for annex+https on connection failure".
 """]]

comment
diff --git a/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_4_fe3fddedf6ece246da6ecdc9bab6b78c._comment b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_4_fe3fddedf6ece246da6ecdc9bab6b78c._comment
new file mode 100644
index 0000000000..1dac830f41
--- /dev/null
+++ b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_4_fe3fddedf6ece246da6ecdc9bab6b78c._comment
@@ -0,0 +1,14 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 4"""
+ date="2026-11-03T14:46:56Z"
+ content="""
+The CHANGELOG file is what you want I think.
+
+I've updated the website re MacPorts.
+
+I suspect it may even need macOS 15. That's the OS that the build is
+happening on. I don't think it makes sense to include the supported OS
+version in the directory name really; it's unlikely there would be 2
+incompatible versions that both would have builds happening.
+"""]]

comment
diff --git a/doc/bugs/testremote_fails_with_p2phttp_remote/comment_1_02958ee913c13b30fcfeaab6054155f5._comment b/doc/bugs/testremote_fails_with_p2phttp_remote/comment_1_02958ee913c13b30fcfeaab6054155f5._comment
new file mode 100644
index 0000000000..b1d9b57b8b
--- /dev/null
+++ b/doc/bugs/testremote_fails_with_p2phttp_remote/comment_1_02958ee913c13b30fcfeaab6054155f5._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2026-11-04T16:28:52Z"
+ content="""
+bisected to [[!f0c046f016aab6485eea57101df953895728e8b4]],
+"re-probing of annex.url for annex+https on connection failure".
+"""]]

initial report on s3 import oddity
diff --git a/doc/todo/provide_more_info_on___34__..._not_available_in_S3_...__34__.mdwn b/doc/todo/provide_more_info_on___34__..._not_available_in_S3_...__34__.mdwn
new file mode 100644
index 0000000000..757718bd1a
--- /dev/null
+++ b/doc/todo/provide_more_info_on___34__..._not_available_in_S3_...__34__.mdwn
@@ -0,0 +1,143 @@
+I am running a simple script which does
+
+```
+git-annex import master --from s3-dandiarchive
+git merge --no-edit s3-dandiarchive/master
+```
+
+but some (?) cron runs end up e.g. with 
+
+```
+Subject: Cron <dandi@drogon> chronic flock -E 0 -e -n /home/dandi/.run/manifests-update.lock /mnt/backup/dandi/dandiset-manifests/code/update
+
+list s3-dandiarchive ok
+import s3-dandiarchive 001412/draft/assets.jsonld
+^M0%    1017 B            7 KiB/s 2h38m^M14%   8.59 MiB         43 MiB/s 1s   ^M39%   23.99 MiB        65 MiB/s 0s   ^M58%   35.54 MiB        58 MiB/s 0s   ^M78%   47.99 MiB        58 MiB/s 0s   ^M91%   55.99 MiB        37
+MiB/s 0s   ^M100%  61.6 MiB        107 MiB/s 0s   ^M                                     ^Mok
+import s3-dandiarchive 001412/draft/assets.yaml
+
+^M^Mfailed
+import s3-dandiarchive 001412/draft/collection.jsonld
+^M0%    1020 B           10 KiB/s 3m42s^M100%  2.26 MiB         82 MiB/s 0s   ^M                                     ^Mok
+import s3-dandiarchive 001412/draft/dandiset.jsonld
+^M55%   1023 B           19 KiB/s 0s^M100%  1.82 KiB          3 MiB/s 0s^M                                  ^Mok
+import s3-dandiarchive 001412/draft/dandiset.yaml
+^M56%   1 KiB            19 KiB/s 0s^M100%  1.78 KiB          5 MiB/s 0s^M                                  ^Mok
+
+(recording state in git...)
+  requested version of object is not available in S3 bucket
+  Failed to import some files from s3-dandiarchive. Re-run command to resume import.
+import: 1 failed
+```
+
+and then when I run "manually" -- all is good:
+
+```
+dandi@drogon:~$ flock -E 0 -e -n /home/dandi/.run/manifests-update.lock /mnt/backup/dandi/dandiset-manifests/code/update
+list s3-dandiarchive ok
+import s3-dandiarchive 001412/draft/assets.jsonld
+ok
+import s3-dandiarchive 001412/draft/assets.yaml
+ok
+import s3-dandiarchive 001412/draft/collection.jsonld
+ok
+import s3-dandiarchive 001412/draft/dandiset.jsonld
+ok
+import s3-dandiarchive 001412/draft/dandiset.yaml
+ok
+update refs/remotes/s3-dandiarchive/master ok
+(recording state in git...)
+Merge made by the 'ort' strategy.
+ 001412/draft/assets.jsonld     |    2 +-
+ 001412/draft/assets.yaml       | 3322 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
+ 001412/draft/collection.jsonld |    2 +-
+ 001412/draft/dandiset.jsonld   |    2 +-
+ 001412/draft/dandiset.yaml     |    4 +-
+ 5 files changed, 3325 insertions(+), 7 deletions(-)
+
+```
+
+Switched to most recent and ran twice in row:
+
+```
+(venv-10.20261102) dandi@drogon:/mnt/backup/dandi/dandiset-manifests$ /mnt/backup/dandi/dandiset-manifests/code/update
+I: Annex version 10.20261026-g351f69fa638bde0d2f68ee5b8bab109c75945331
+list s3-dandiarchive ok
+import s3-dandiarchive 001412/draft/assets.jsonld
+ok
+import s3-dandiarchive 001412/draft/assets.yaml
+
+  requested version of object is not available in S3 bucket
+failed
+import s3-dandiarchive 001412/draft/collection.jsonld
+ok
+import s3-dandiarchive 001412/draft/dandiset.jsonld
+
+  requested version of object is not available in S3 bucket
+failed
+import s3-dandiarchive 001412/draft/dandiset.yaml
+ok
+
+  Failed to import some files from s3-dandiarchive. Re-run command to resume import.
+(recording state in git...)
+import: 2 failed
+(venv-10.20261102) dandi@drogon:/mnt/backup/dandi/dandiset-manifests$ /mnt/backup/dandi/dandiset-manifests/code/update
+I: Annex version 10.20261026-g351f69fa638bde0d2f68ee5b8bab109c75945331
+list s3-dandiarchive ok
+import s3-dandiarchive 001412/draft/assets.jsonld
+ok
+import s3-dandiarchive 001412/draft/assets.yaml
+ok
+import s3-dandiarchive 001412/draft/collection.jsonld
+ok
+import s3-dandiarchive 001412/draft/dandiset.jsonld
+ok
+update refs/remotes/s3-dandiarchive/master ok
+(recording state in git...)
+Merge made by the 'ort' strategy.
+ 001412/draft/assets.jsonld     |   2 +-
+ 001412/draft/assets.yaml       | 450 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
+ 001412/draft/collection.jsonld |   2 +-
+ 001412/draft/dandiset.jsonld   |   2 +-
+ 001412/draft/dandiset.yaml     |   4 +-
+ 5 files changed, 449 insertions(+), 11 deletions(-)
+```
+
+
+I am not certain now I have a full backup of that bucket given such behaviors, and it remains unclear what caused it. 
+
+I think if git-annex at least provided more info -- which key and which versionId were "not available" -- it might have helped to shine the light... I guess there might be some form of interaction with our "trailing delete" policy where older versions get removed but it is unlikely there is a race condition this fast.
+
+bucket `dandiarchive` is versioned and most of the files are openly available there
+
+```
+(venv-10.20261102) dandi@drogon:/mnt/backup/dandi/dandiset-manifests$ git show git-annex:remote.log
+09b87154-c650-46d1-a036-6e03c56c0b1a bucket=dandiarchive datacenter=US encryption=none fileprefix=dandisets/ host=s3.amazonaws.com importtree=yes name=s3-dandiarchive port=80 publicurl=https://dandiarchive.s3.amazonaws.com/ region=us-east-2 signature=v4 storageclass=STANDARD type=S3 timestamp=1764702488s
+```
+
+and this particular "folder" seems to be  accessible, e.g. since can browse/see on [quilt data](https://app.quilt.bio/b/dandiarchive/tree/dandisets/001412/draft/)
+
+the "problem" is (yet to report/have it addressed) is that we keep rewriting those files too fast too often, so e.g. this one has 
+
+```
+dandi@drogon:~$ aws s3api list-object-versions     --bucket dandiarchive     --prefix dandisets/001412/draft/assets.yaml     --query "Versions[].{VersionId: VersionId, LastModified: LastModified}"     --output table | wc -l 
+1086
+
+dandi@drogon:~$ aws s3api list-object-versions     --bucket dandiarchive     --prefix dandisets/001412/draft/assets.yaml     --query "Versions[].{VersionId: VersionId, LastModified: LastModified}"     --output table | head -n 6
+-------------------------------------------------------------------
+|                       ListObjectVersions                        |
++----------------------------+------------------------------------+
+|        LastModified        |             VersionId              |
++----------------------------+------------------------------------+
+|  2026-11-03T16:40:06+00:00 |  pBuWBb3vXNGKn5kW_40eWg05mWg1k_IQ  |
+dandi@drogon:~$ aws s3api list-object-versions     --bucket dandiarchive     --prefix dandisets/001412/draft/assets.yaml     --query "Versions[].{VersionId: VersionId, LastModified: LastModified}"     --output table | tail -n 3
+|  2026-11-02T00:00:51+00:00 |  yyNkMORu2sgOxjeMq7k29Onj_Jw1MB13  |
+|  2026-11-01T23:59:50+00:00 |  rw5Oxaff7g_fK5rF17Gi7Mfv.x1pLsp5  |
++----------------------------+------------------------------------+
+```
+
+but they are all there and should be accessible so I am not sure why git-annex errors out. Indeed a new version of the key might arrive while annex is importing a prior one (might want even to gate versions may be given a timestamp?), but that IMHO warrants a warning (since it still managed to import) not an error.
+
+
+[[!meta author=yoh]]
+[[!tag projects/dandi]]

diff --git a/doc/bugs/testremote_fails_with_p2phttp_remote.mdwn b/doc/bugs/testremote_fails_with_p2phttp_remote.mdwn
index e204c1c715..30d61e861b 100644
--- a/doc/bugs/testremote_fails_with_p2phttp_remote.mdwn
+++ b/doc/bugs/testremote_fails_with_p2phttp_remote.mdwn
@@ -148,7 +148,7 @@ Remote Tests
     removeKey:                                       git-credential-oauth 0.15.1
 input: map[host:atris.fz-juelich.de:443 path:git-annex-p2phttp protocol:annex+https wwwauth[]:Basic realm="Gitea"]
 Please complete authentication in your browser...
-annex+https://atris.fz-juelich.de:443/login/oauth/authorize?client_id=a4792ccc-144e-407e-86c9-5e7d8d9c3269&code_challenge=RZz-MkOeyBMB3dn3es9-ytpsG-IKhPKkY0p7-hq1rA8&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3A34579&response_type=code&state=5cIyb0zY-zVUjYjRWngHTDJXAdLoUdhjAPcBqBVDzcU
+annex+https://atris.fz-juelich.de:443/login/oauth/authorize?<redacted>
 Unable to open browser using 'xdg-open': exit status 4
 """]]
 

diff --git a/doc/bugs/testremote_fails_with_p2phttp_remote.mdwn b/doc/bugs/testremote_fails_with_p2phttp_remote.mdwn
new file mode 100644
index 0000000000..e204c1c715
--- /dev/null
+++ b/doc/bugs/testremote_fails_with_p2phttp_remote.mdwn
@@ -0,0 +1,160 @@
+### Please describe the problem in your own words.
+
+`git annex testremote --test-readonly <file> <remote>` and also the non-readonly variant recently started to fail in the Forgejo-aneksajo test suite after NeuroDebian's update to 10.20261026. This is the error I am seeing:
+
+```
+2026-10-31T13:26:29.6800139Z Remote Tests
+2026-10-31T13:26:29.6802324Z   unavailable remote
+2026-10-31T13:26:29.9914722Z     removeKey:                                       FAIL (0.31s)
+2026-10-31T13:26:29.9922352Z       ./Command/TestRemote.hs:463:
+2026-10-31T13:26:29.9930303Z       (got: Right ())
+2026-10-31T13:26:29.9930672Z       Use -p '/unavailable remote.removeKey/' to rerun this test only.
+2026-10-31T13:26:30.0465183Z     storeKey:                                        FAIL (0.05s)
+2026-10-31T13:26:30.0467492Z       ./Command/TestRemote.hs:463:
+2026-10-31T13:26:30.0467710Z       (got: Right ())
+2026-10-31T13:26:30.0468003Z       Use -p '/unavailable remote.storeKey/' to rerun this test only.
+2026-10-31T13:26:30.0663456Z     checkPresent:                                    FAIL (0.01s)
+2026-10-31T13:26:30.0665582Z       ./Command/TestRemote.hs:463:
+2026-10-31T13:26:30.0665808Z       (got: Right True)
+2026-10-31T13:26:30.0667078Z       Use -p '/checkPresent/' to rerun this test only.
+```
+(the rest show "OK", `3 out of 35 tests failed (0.53s)`)
+
+After testing different versions I found that `10.20260901.post1` still worked and `10.20261020` installed from PyPI exhibits the error. There is another version `10.20261005` in between that doesn't have a package in PyPI so I couldn't easily test. Since it mentions changes on testremote it seems likely the issue started there.
+
+I also see the issue with a local clone and p2phttp, see reproducer.
+
+
+### What steps will reproduce the problem?
+
+To setup a to-be-cloned repo and start p2phttp:
+
+[[!format bash """
+#!/usr/bin/env bash
+set -euxo pipefail
+
+mkdir repo
+cd repo
+git init
+git annex init
+echo test > test.txt
+git annex add test.txt
+git commit -m commit
+git annex p2phttp --bind 127.0.0.1 --port 54321 -J2 --debug --wideopen
+"""]]
+
+To clone and run testremote:
+
+[[!format bash """
+#!/usr/bin/env bash
+set -euxo pipefail
+
+git clone repo repo-clone
+cd repo-clone
+git config annex.security.allowed-ip-addresses 127.0.0.1
+git config remote.origin.annexUrl annex+http://127.0.0.1:54321
+git annex get test.txt
+git annex testremote --test-readonly test.txt origin
+"""]]
+
+
+### What version of git-annex are you using? On what operating system?
+
+Multiple, the issue is present in 10.20261020 and later, it is not present in 10.20260901 and before.
+
+
+### Please provide any additional information below.
+
+Full transcript from the clone and testremote:
+
+[[!format sh """
+# If you can, paste a complete transcript of the problem occurring here.
+# If the problem is with the git-annex assistant, paste in .git/annex/daemon.log
+
+$ ./clone.bash 
++ git clone repo repo-clone
+Klone nach 'repo-clone'...
+Fertig.
++ cd repo-clone
++ git config annex.security.allowed-ip-addresses 127.0.0.1
++ git config remote.origin.annexUrl annex+http://127.0.0.1:54321
++ git annex get test.txt
+get test.txt (from origin...) 
+ok                                
+(recording state in git...)
++ git annex testremote --test-readonly test.txt origin
+Remote Tests
+  unavailable remote
+    removeKey:                                       FAIL
+      ./Command/TestRemote.hs:463:
+      (got: Right ())
+      Use -p '/unavailable remote.removeKey/' to rerun this test only.
+    storeKey:                                        FAIL (0.06s)
+      ./Command/TestRemote.hs:463:
+      (got: Right ())
+      Use -p '/unavailable remote.storeKey/' to rerun this test only.
+    checkPresent:                                    FAIL
+      ./Command/TestRemote.hs:463:
+      (got: Right True)
+      Use -p '/checkPresent/' to rerun this test only.
+    retrieveKeyFile:                                 OK
+    retrieveKeyFileCheap:                            OK
+  key size Just 5; remote
+    removeKey when not present:                      OK
+    present False:                                   OK
+    storeKey:                                        OK
+    present True:                                    OK
+    storeKey when already present:                   OK
+    present True:                                    OK
+    retrieveKeyFile:                                 OK (0.02s)
+    fsck downloaded object:                          OK
+    retrieveKeyFile resume from 0:                   OK
+    fsck downloaded object:                          OK
+    retrieveKeyFile resume from 33%:                 OK
+    fsck downloaded object:                          OK
+    retrieveKeyFile resume from end:                 OK
+    fsck downloaded object:                          OK
+    removeKey when present:                          OK
+    present False:                                   OK
+  exporttree=yes; key size Just 5; key size Just 5
+    check present export when not present:           OK
+    remove export when not present:                  OK
+    store export:                                    OK
+    check present export after store:                OK
+    store export when already present:               OK
+    retrieve export:                                 OK
+    store new content to export:                     OK
+    check present export after store of new content: OK
+    retrieve export new content:                     OK
+    remove export:                                   OK
+    check present export after remove:               OK
+    retrieve export fails after removal:             OK
+    remove export directory:                         OK
+    remove export directory that is already removed: OK
+
+3 out of 35 tests failed (0.12s)
+testremote: 1 failed
+[ble: exit 1]
+
+# End of transcript or log.
+"""]]
+
+Additionally I've tested this against a public repository hosted on <https://atris.fz-juelich.de> and get a somewhat different error:
+
+[[!format sh """
+$ git annex testremote --test-readonly outputs/ccm/nhsat-nino34-e10.pdf origin
+Remote Tests
+  unavailable remote
+    removeKey:                                       git-credential-oauth 0.15.1
+input: map[host:atris.fz-juelich.de:443 path:git-annex-p2phttp protocol:annex+https wwwauth[]:Basic realm="Gitea"]
+Please complete authentication in your browser...
+annex+https://atris.fz-juelich.de:443/login/oauth/authorize?client_id=a4792ccc-144e-407e-86c9-5e7d8d9c3269&code_challenge=RZz-MkOeyBMB3dn3es9-ytpsG-IKhPKkY0p7-hq1rA8&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3A34579&response_type=code&state=5cIyb0zY-zVUjYjRWngHTDJXAdLoUdhjAPcBqBVDzcU
+Unable to open browser using 'xdg-open': exit status 4
+"""]]
+
+To me it looks like it is not replacing the remote url with a proper unavailable one for these tests, leading to the ATRIS case breaking on git-credential-oauth and the local case breaking because the operations finish successfully when they shouldn't.
+
+### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
+
+
+[[!tag projects/ICE4]]

close
diff --git a/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina.mdwn b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina.mdwn
index d96798b9b6..c5f066da40 100644
--- a/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina.mdwn
+++ b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina.mdwn
@@ -44,3 +44,5 @@ ewen@dagoba:~$
 Yes, git-annex has been very useful for years!
 
 (Also, related to [my previous bug report about more recent macOS Intel, dying with Signal 4](https://git-annex.branchable.com/bugs/10.20261006__58___addurl__58___git-annex_died_of_signal_4/) I can report that HomeBrew have updated their build options to no longer pin a pre-release package, and seems to be back to building against an older `crypton` without the problem.  So that's encouraging!)
+
+> Renamed the build. [[done]] --[[Joey]]

moved
diff --git a/doc/builds.mdwn b/doc/builds.mdwn
index 13947e0ffd..be8edaf207 100644
--- a/doc/builds.mdwn
+++ b/doc/builds.mdwn
@@ -19,7 +19,7 @@
 <iframe width=1024 height=40em scrolling=no frameborder=0 marginheight=0 marginwidth=0 src="https://downloads.kitenet.net/git-annex/autobuild/arm64-ancient/build-version">
 </iframe>
 <h2>OSX</h2>
-<iframe width=1024 height=40em scrolling=no frameborder=0 marginheight=0 marginwidth=0 src="https://downloads.kitenet.net/git-annex/autobuild/x86_64-apple-catalina/build-version">
+<iframe width=1024 height=40em scrolling=no frameborder=0 marginheight=0 marginwidth=0 src="https://downloads.kitenet.net/git-annex/autobuild/OSX/build-version">
 </iframe>
 <h2>Windows</h2>
 <iframe width=1024 height=40em scrolling=no frameborder=0 marginheight=0 marginwidth=0 src="https://downloads.kitenet.net/git-annex/autobuild/windows/build-version">

typo
diff --git a/doc/install/OSX.mdwn b/doc/install/OSX.mdwn
index 948e922632..c72a1f08bc 100644
--- a/doc/install/OSX.mdwn
+++ b/doc/install/OSX.mdwn
@@ -21,7 +21,7 @@ several more. Handy if you don't otherwise have git installed.
 A daily autobuild is also available, run by [[Joey]]:
 
 * [git-annex.dmg](https://downloads.kitenet.net/git-annex/autobuild/OSX/git-annex.dmg)
-  ([build logs](https://github.com/joeyh/gitannexbuilder/actions/workflows/build-macos-intel.yaml)
+  ([build logs](https://github.com/joeyh/gitannexbuilder/actions/workflows/build-macos-intel.yaml))
 
 ## download security
 

fix link
diff --git a/doc/install/OSX.mdwn b/doc/install/OSX.mdwn
index 8cbae34424..948e922632 100644
--- a/doc/install/OSX.mdwn
+++ b/doc/install/OSX.mdwn
@@ -21,7 +21,7 @@ several more. Handy if you don't otherwise have git installed.
 A daily autobuild is also available, run by [[Joey]]:
 
 * [git-annex.dmg](https://downloads.kitenet.net/git-annex/autobuild/OSX/git-annex.dmg)
-  ([build logs](https://github.com/joeyh/gitannexbuilder/actions/workflows/build-macos.yaml?query=Build+git-annex+on+macOS))
+  ([build logs](https://github.com/joeyh/gitannexbuilder/actions/workflows/build-macos-intel.yaml)
 
 ## download security
 

move OSX build out of ancient version directory
diff --git a/Build/DistributionUpdate.hs b/Build/DistributionUpdate.hs
index 7b24977d03..10311f1b2e 100644
--- a/Build/DistributionUpdate.hs
+++ b/Build/DistributionUpdate.hs
@@ -43,7 +43,7 @@ signingKey = "89C809CB"
 autobuilds :: [(URLString, OsPath)]
 autobuilds = 
 	(map linuxarch ["i386", "amd64", "armhf", "arm64", "arm64-ancient"]) ++
-	[ (autobuild "x86_64-apple-catalina/git-annex.dmg", literalOsPath "git-annex/OSX/current/10.15_Catalina/git-annex.dmg")
+	[ (autobuild "OSX/git-annex.dmg", literalOsPath "git-annex/OSX/current/git-annex.dmg")
 	, (autobuild "windows/git-annex-installer.exe", literalOsPath "git-annex/windows/current/git-annex-installer.exe")
 	]
   where
diff --git a/doc/install/OSX.mdwn b/doc/install/OSX.mdwn
index f935488a2f..8cbae34424 100644
--- a/doc/install/OSX.mdwn
+++ b/doc/install/OSX.mdwn
@@ -3,7 +3,7 @@
 [[!img /assistant/osx-app.png align=right link=/assistant]]
 For easy installation, use the prebuilt app bundle.
 
-* 10.15 Catalina: [git-annex.dmg](https://downloads.kitenet.net/git-annex/OSX/current/10.15_Catalina/git-annex.dmg)
+* [git-annex.dmg](https://downloads.kitenet.net/git-annex/OSX/current/git-annex.dmg)
 
 To run the [[git-annex_assistant|/assistant]], just
 install the app, look for the icon, and start it up. 
@@ -20,7 +20,7 @@ several more. Handy if you don't otherwise have git installed.
 
 A daily autobuild is also available, run by [[Joey]]:
 
-* [git-annex.dmg](https://downloads.kitenet.net/git-annex/autobuild/x86_64-apple-catalina/git-annex.dmg)
+* [git-annex.dmg](https://downloads.kitenet.net/git-annex/autobuild/OSX/git-annex.dmg)
   ([build logs](https://github.com/joeyh/gitannexbuilder/actions/workflows/build-macos.yaml?query=Build+git-annex+on+macOS))
 
 ## download security

link to MacPorts build
diff --git a/doc/install/OSX.mdwn b/doc/install/OSX.mdwn
index 6f87155477..f935488a2f 100644
--- a/doc/install/OSX.mdwn
+++ b/doc/install/OSX.mdwn
@@ -34,8 +34,7 @@ git-annex is [[available in Homebrew|Homebrew]].
 
 ## using MacPorts
 
-git-annex is not available in MacPorts, but can be built from source using
-MacPorts tools. See [[MacPorts]].
+git-annex is [[available in MacPorts|MacPorts]].
 
 ## building it yourself
 
diff --git a/doc/install/OSX/MacPorts.mdwn b/doc/install/OSX/MacPorts.mdwn
index dd6db8b0eb..83b0edcd90 100644
--- a/doc/install/OSX/MacPorts.mdwn
+++ b/doc/install/OSX/MacPorts.mdwn
@@ -1,7 +1,12 @@
-This is not a recommended way to install git-annex. Use [[HomeBrew]] or the
-prebuilt app bundle instead.
+[MacPorts has a build of git-annex](https://ports.macports.org/port/git-annex/details/)
 
-But if you really want to use MacPorts:
+## buiding git-annex from sources
+
+This is the old recipe for building git-annex from source, using
+packages from MacPorts. Useful if you want a newer version than the version
+in MacPorts.
+
+(This may be out of date.)
 
 Install the Haskell Platform from [[http://hackage.haskell.org/platform/mac.html]].
 The version provided by Macports is too old to work with current versions of git-annex.

todo
diff --git a/doc/todo/display_glitches_with_-J_and_non-unicode.mdwn b/doc/todo/display_glitches_with_-J_and_non-unicode.mdwn
new file mode 100644
index 0000000000..9eb6fd0cfa
--- /dev/null
+++ b/doc/todo/display_glitches_with_-J_and_non-unicode.mdwn
@@ -0,0 +1,13 @@
+When not in a unicode locale and -J is enabled, concurrent-output is not
+used. This can lead to some display glitches. For example, with 
+`LANG=C git-annex copy --to foo -J2`, it will sometimes output
+
+    ok
+    ok
+
+And sometimes
+
+    (to foo...) (to foo...)
+
+The fix for this will probably be to prevent more than 1 job when
+in such a locale. --[[Joey]]

Avoid display issues with multiple progress meters when not in a unicode locale
Progress meters overwrote one another. Avoid displaying any progress meters
when concurrency is enabled and not in a unicode locale. But, still display
progress meters when there is no concurrency.
diff --git a/CHANGELOG b/CHANGELOG
index 5a9b8a7d2e..7b0074d774 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,6 +10,8 @@ git-annex (10.20261027) UNRELEASED; urgency=medium
     contained unicode multibyte space characters.
   * Fix buggy handling of a submodule in a linked git worktree.
   * Added transfers command, displays a live view of transfers.
+  * Avoid display issues with multiple progress meters when not in a
+    unicode locale.
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/Messages/Concurrent.hs b/Messages/Concurrent.hs
index b4f0dab3d0..7ea5e63e70 100644
--- a/Messages/Concurrent.hs
+++ b/Messages/Concurrent.hs
@@ -138,8 +138,12 @@ withConcurrentOutput a =
 
 	cleanup = setconcurrentoutputenabled False
 
-	setconcurrentoutputenabled b = Annex.changeState $ \s ->
-                s { Annex.output = (Annex.output s) { concurrentOutputEnabled = b } }
+	setconcurrentoutputenabled b = Annex.changeState $ \s -> s
+		{ Annex.output = (Annex.output s)
+			{ concurrentOutputEnabled = b
+			, disableProgressMeters = not b
+			}
+		}
 
 {- Hide any currently displayed console regions while running the action,
  - so that the action can use the console itself. -}
diff --git a/Messages/Progress.hs b/Messages/Progress.hs
index 78e7d5f75b..47429fa5ed 100644
--- a/Messages/Progress.hs
+++ b/Messages/Progress.hs
@@ -106,6 +106,11 @@ metered'
 metered' st setclear othermeterupdate msize bwlimit showoutput a = go st
   where
 	go (MessageState { outputType = QuietOutput }) = nometer
+	go (MessageState { outputType = NormalOutput, disableProgressMeters = True }) = do
+		meter <- liftIO $ mkMeter msize dontDisplayMeter
+		m <- liftIO $ rateLimitMeterUpdate consoleratelimit meter $
+			updateMeter meter
+		a meter =<< mkmeterupdate m
 	go (MessageState { outputType = NormalOutput, concurrentOutputEnabled = False }) = do
 		showoutput
 		meter <- liftIO $ mkMeter msize $ 
diff --git a/Types/Messages.hs b/Types/Messages.hs
index 3f830fc839..a5a29a89d5 100644
--- a/Types/Messages.hs
+++ b/Types/Messages.hs
@@ -42,6 +42,7 @@ data SideActionBlock = NoBlock | StartBlock | InBlock
 data MessageState = MessageState
 	{ outputType :: OutputType
 	, concurrentOutputEnabled :: Bool
+	, disableProgressMeters :: Bool
 	, sideActionBlock :: SideActionBlock
 	, consoleRegion :: Maybe ConsoleRegion
 	, consoleRegionErrFlag :: Bool
@@ -56,6 +57,7 @@ newMessageState = do
 	return $ MessageState
 		{ outputType = NormalOutput
 		, concurrentOutputEnabled = False
+		, disableProgressMeters = False
 		, sideActionBlock = NoBlock
 		, consoleRegion = Nothing
 		, consoleRegionErrFlag = False
diff --git a/Utility/Metered.hs b/Utility/Metered.hs
index 672a1d6da7..660f144d15 100644
--- a/Utility/Metered.hs
+++ b/Utility/Metered.hs
@@ -43,6 +43,7 @@ module Utility.Metered (
 	mkMeter,
 	setMeterTotalSize,
 	updateMeter,
+	dontDisplayMeter,
 	displayMeterHandle,
 	clearMeterHandle,
 	bandwidthMeter,
@@ -496,6 +497,10 @@ updateMeter (Meter totalsizev sv bv displaymeter) new = do
 		totalsize <- readMVar totalsizev
 		displaymeter bv totalsize oldms curms
 
+-- | Don't display a meter.
+dontDisplayMeter :: DisplayMeter
+dontDisplayMeter _ _ _ _ = return ()
+
 -- | Display meter to a Handle.
 displayMeterHandle :: Handle -> RenderMeter -> DisplayMeter
 displayMeterHandle h rendermeter v msize old new = do
diff --git a/doc/todo/non_unicode_locale_progress.mdwn b/doc/todo/non_unicode_locale_progress.mdwn
new file mode 100644
index 0000000000..a67c2c9835
--- /dev/null
+++ b/doc/todo/non_unicode_locale_progress.mdwn
@@ -0,0 +1,11 @@
+When in a non unicode locale, concurrent-output cannot be used. When -J is
+used, and there are progress meters displayed, this leads to the meters
+being written at whatever the current cursor position happens to be,
+which messes up the display.
+
+It might be best to fix this by just not displaying progress meters when in
+a non-unicode locale and when -J is used. That was the approach taken
+in [[!commit 0f18636c8a85526259e9d75f6164c66edbca676b]].
+--[[Joey]]
+
+> [done]]

done
diff --git a/doc/todo/git-annex_transfers_better_progress.mdwn b/doc/todo/git-annex_transfers_better_progress.mdwn
index 69ecde09df..76d7c33187 100644
--- a/doc/todo/git-annex_transfers_better_progress.mdwn
+++ b/doc/todo/git-annex_transfers_better_progress.mdwn
@@ -11,9 +11,10 @@ count. This would mean writing some data to disk, but well, during a
 transfer a lot of data is already going to/from disk, so updating the file
 once per second would probably be unnoticable.
 
-Seems that readTransferInfo never populates the bytesComplete field even if
-it got written. And while mkProgressUpdater should update the file,
+While mkProgressUpdater should update the file,
 it seems it's not?
 
 I have some work toward this in the `transferprogress` branch.
 --[[Joey]]
+
+> [[done]] --[[Joey]]

update
diff --git a/doc/git-annex-transfers.mdwn b/doc/git-annex-transfers.mdwn
index ef2bb79486..ab4fcd21c9 100644
--- a/doc/git-annex-transfers.mdwn
+++ b/doc/git-annex-transfers.mdwn
@@ -10,9 +10,6 @@ git annex transfers
 
 A live view of files that are being transferred from and to remotes.
 
-For files that are being received, this often includes a progress meter.
-No progress information is available for files that are being sent.
-
 For example:
 
     receive foo (from darkstar)

Added a comment: show git-annex release notes for 2026
diff --git a/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_3_2bb60c3fe47bf1f78a33cef2ae54f607._comment b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_3_2bb60c3fe47bf1f78a33cef2ae54f607._comment
new file mode 100644
index 0000000000..2c6da45e0c
--- /dev/null
+++ b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_3_2bb60c3fe47bf1f78a33cef2ae54f607._comment
@@ -0,0 +1,26 @@
+[[!comment format=mdwn
+ username="ewen"
+ avatar="http://cdn.libravatar.org/avatar/605b2981cb52b4af268455dee7a4f64e"
+ subject="show git-annex release notes for 2026"
+ date="2026-11-03T05:41:13Z"
+ content="""
+Also in case it helps someone else later, as best I can tell the release notes are in the git repository as `doc/news/version_10.20xxxxxx.mdwn`, but it seems like the older release notes get deleted when newer ones are added (at a guess, to keep ~4 release notes showing up on the [news page](https://git-annex.branchable.com/news/)).
+
+This makes reviewing the release notes since an older release somewhat more difficult than it might be, as it seems like the only record of the older release notes is as deleted files, in the git history.
+
+I ended up coming up with:
+
+```
+git clone git://git-annex.branchable.com/ git-annex
+cd git-annex/doc/news
+git log --stat f1985eabce137adb8851cbbaea1fa8474406f7b5..HEAD . | egrep '^commit|10.2026.*\+\+\+\+\+\+' | grep -B 1 doc/news | grep -v '^--' | sed 'N;s/\n/ /; s/^commit/git show/; s/  doc/:doc/; s/mdwn.*$/mdwn/' | tac | sh -x
+```
+
+to see the release notes for 2026, in chronologically increasing order (ie oldest to newest).  Where the magic git commit hash was found by searching with \"git log\" for the last 2025 / first 2026 commit to `doc/news`, since the version I found that ran for me (on macOS 10.15) was the last 2025 standalone build, and thus I wanted to know what had changed since then.
+
+(Possibly older news articles could be `git mv`-ed to an archive directory rather than `git rm`-ed to make this easier in future, without cluttering up the news page?)
+
+Anyway skim reading the least year's worth of release notes (from the above command, and some slight tidying up), I don't see much that an internal-only git annex remote-only would particularly need *before* the various October 2026 fixes (which I can't easily have due to build dependencies not supporting macOS 10.15).  The only thing that stood out as potentially desirable is the hash performance improvements in `git-annex 10.20260624`, but for this mostly-archivable, infrequently updated use case I can probably live without those too.
+
+Ewen
+"""]]

Added a comment: git-annex 10.20251118 does run on macOS 10.15 / Catalina
diff --git a/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_2_937d6944f215e1cd24d1ec5087148a33._comment b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_2_937d6944f215e1cd24d1ec5087148a33._comment
new file mode 100644
index 0000000000..f31fc64690
--- /dev/null
+++ b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_2_937d6944f215e1cd24d1ec5087148a33._comment
@@ -0,0 +1,31 @@
+[[!comment format=mdwn
+ username="ewen"
+ avatar="http://cdn.libravatar.org/avatar/605b2981cb52b4af268455dee7a4f64e"
+ subject="git-annex 10.20251118 does run on macOS 10.15 / Catalina"
+ date="2026-11-03T04:40:59Z"
+ content="""
+For anyone else finding this issue and wondering, it appears that at least git-annex standalone build for macOS from 2025 *does* run on macOS 10.15 / Catalina still.
+
+```
+wen@dagoba:~$ uname -a
+Darwin dagoba 19.6.0 Darwin Kernel Version 19.6.0: Tue Jun 21 21:18:39 PDT 2022; root:xnu-6153.141.66~1/RELEASE_X86_64 x86_64
+ewen@dagoba:~$ git annex version
+git-annex version: 10.20251118-g9ba1afcff4f513d938763a45488ab392747b2704
+build flags: Assistant Webapp Pairing FsEvents TorrentParser MagicMime Benchmark Feeds Testsuite S3 WebDAV Servant OsPath
+dependency versions: aws-0.25.1 bloomfilter-2.0.1.2 crypton-1.0.4 DAV-1.3.4 feed-1.3.2.1 ghc-9.10.2 http-client-0.7.19 persistent-sqlite-2.13.3.1 torrent-10000.1.3 uuid-1.3.16 yesod-1.6.2.1
+key/value backends: SHA256E SHA256 SHA512E SHA512 SHA224E SHA224 SHA384E SHA384 SHA3_256E SHA3_256 SHA3_512E SHA3_512 SHA3_224E SHA3_224 SHA3_384E SHA3_384 SKEIN256E SKEIN256 SKEIN512E SKEIN512 BLAKE2B256E BLAKE2B256 BLAKE2B512E BLAKE2B512 BLAKE2B160E BLAKE2B160 BLAKE2B224E BLAKE2B224 BLAKE2B384E BLAKE2B384 BLAKE2BP512E BLAKE2BP512 BLAKE2S256E BLAKE2S256 BLAKE2S160E BLAKE2S160 BLAKE2S224E BLAKE2S224 BLAKE2SP256E BLAKE2SP256 BLAKE2SP224E BLAKE2SP224 SHA1E SHA1 MD5E MD5 WORM URL GITBUNDLE GITMANIFEST VURL X*
+remote types: git gcrypt p2p S3 bup directory rsync web bittorrent webdav adb tahoe glacier ddar git-lfs httpalso borg rclone hook external compute mask
+operating system: darwin x86_64
+supported repository versions: 8 9 10
+upgrade supported from repository versions: 0 1 2 3 4 5 6 7 8 9 10
+ewen@dagoba:~$ 
+```
+
+Obtained by following the [instructions on the kitenet download page](https://downloads.kitenet.net/) to check out the git annex containing the references to `archive.org` downloads of old builds, and then using `git log` to find an \"end of 2025\" commit of `git-annex.dmg`, doing a `git checkout` of that commit, and then a `git annex get git-annex.dmg` to download the file (from the `web`, aka `archive.org`).  Note that since the uploads to `archive.org` are a git annex remote, they're uploaded by hash filename, and not particularly navigable without the git annex archive as metadata.
+
+(While \"a year old\" might seem rather old, the \"old laptop as a git annex disk server\" this upcycled laptop is replacing has an even older git annex, so this is still a substantial version upgrade!)
+
+Likely some of the earlier `10.2026xxxx` will also run on macOS 10.15 / Catalina, but since I'm forced to run a git-annex before the \"late 2026\" security updates (eg, eMudhra \"4 as a private key\") anyway, in this specific use case (internal only git annex remote) I'm not sure there's much to be gained by trying to find the newest build that still runs. (My guess is \"before macOS 27 came out\", but likely older than that, since it seems \"dependency builds switched to depending on macOS 13/14 SDK\" is the breaking things.  And from what I've found of NodeJS / dotNet etc similar bugs that specific macOS SDK function was added in macOS 13 or earlier.)
+
+Ewen
+"""]]

Added a comment: MacPorts build of git-annex
diff --git a/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_1_160cbf020f8fc0b4131d91b69ffebd01._comment b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_1_160cbf020f8fc0b4131d91b69ffebd01._comment
new file mode 100644
index 0000000000..bb737a8f95
--- /dev/null
+++ b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina/comment_1_160cbf020f8fc0b4131d91b69ffebd01._comment
@@ -0,0 +1,12 @@
+[[!comment format=mdwn
+ username="ewen"
+ avatar="http://cdn.libravatar.org/avatar/605b2981cb52b4af268455dee7a4f64e"
+ subject="MacPorts build of git-annex"
+ date="2026-11-03T04:07:47Z"
+ content="""
+As additional context, it looks like [MacPorts does now have a build of git-annex](https://ports.macports.org/port/git-annex/details/), although it seems infrequently updated (`Version: 10.20260316`, `Updated: 7 months, 2 weeks ago`).  It too doesn't support macOS 10.15 / Catalina any longer.
+
+But maybe it'd be worth linking from the [OSX page](https://git-annex.branchable.com/install/OSX/) if you think \"a build, but maybe old\" is worth users considering.  (By contrast I think HomeBrew has active git-annex users, as it seems to get updated/rebuilt fairly soon after a new release.)
+
+Ewen
+"""]]

Context for git-annex standalone dying on macOS 10.15 / Catalina
diff --git a/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina.mdwn b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina.mdwn
new file mode 100644
index 0000000000..d96798b9b6
--- /dev/null
+++ b/doc/bugs/macOS__58___standalone__58___does_not_run_on_10.15_Catalina.mdwn
@@ -0,0 +1,46 @@
+### Please describe the problem in your own words.
+
+While setting up an old MacBook Pro (2012) to run as a git-annex disk repository, I found that the [current macOS Standalone build](https://downloads.kitenet.net/git-annex/OSX/current/10.15_Catalina/) no longer runs on macOS 10.15 / Catalina, despite the directory name.
+
+It looks like some of the dependencies used are only built for macOS 14 or later (which is unsurprising, as I believe current macOS Developer tools force macOS 14 or later).  And macOS 10.15 went entirely unsupported earlier this year (not even extended security support), so it's unsurprising it's no longer a build target.  (IIRC macOS 14 is also the oldest version that HomeBrew builds against now.)
+
+I'm reporting it just as a suggestion that maybe the macOS build directory for downloads gets renamed to, eg, macOS 14, which is [macOS Sonomoa](https://en.wikipedia.org/wiki/MacOS_Sonoma), to reflect the current situation.  And the [OSX](https://git-annex.branchable.com/install/OSX/) page gets updated to reflect that, and maybe link to some older build for "old macOS".
+
+(macOS 10.15 is the last macOS version to run on this MacBook Pro 2012, so I'll try digging back into older builds for a git-annex build that will run on macOS 10.15.  Fortunately it's very internal, so latest-security-patches isn't a huge concern.  This is just hardware upcycling while it still works.)
+
+### What steps will reproduce the problem?
+
+On a macOS 10.15.8 (Catalina) system, install the [current macOS Standalone build](https://downloads.kitenet.net/git-annex/OSX/current/10.15_Catalina/), add it to `$PATH`, and then run `git annex version`.  Observe it fails with a missing symbol:
+
+```
+dyld: Symbol not found: __ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE4openEPKcj
+```
+
+apparently from a dependency `.dylib`, which AFAICT is `libbotan-3.13`, and it looks like that dylib dependency was built for macOS 14.
+
+### What version of git-annex are you using? On what operating system?
+
+macOS standalone build in the 10.15_Catalina directory today, dated `2026-10-26 14:37`.  I'd assumed it was 10.20261026, but I think it might be 10.20261021 based on the App embedded metadata.
+
+### Please provide any additional information below.
+
+```
+ewen@dagoba:~$ uname -a
+Darwin dagoba 19.6.0 Darwin Kernel Version 19.6.0: Tue Jun 21 21:18:39 PDT 2022; root:xnu-6153.141.66~1/RELEASE_X86_64 x86_64
+ewen@dagoba:~$ git annex version
+dyld: Symbol not found: __ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE4openEPKcj
+  Referenced from: /Applications/OpenSource/git-annex.app/Contents/MacOS/bundle/D (which was built for Mac OS X 14.0)
+  Expected in: /usr/lib/libc++.1.dylib
+
+error: git-annex died of signal 6
+ewen@dagoba:~$ 
+ewen@dagoba:~$ ls -l /Applications/OpenSource/git-annex.app/Contents/MacOS/bundle/D
+lrwxr-xr-x  1 ewen  admin  43 Oct 26 11:16 /Applications/OpenSource/git-annex.app/Contents/MacOS/bundle/D -> usr/local/opt/botan/lib/libbotan-3.13.dylib
+ewen@dagoba:~$ 
+```
+
+### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
+
+Yes, git-annex has been very useful for years!
+
+(Also, related to [my previous bug report about more recent macOS Intel, dying with Signal 4](https://git-annex.branchable.com/bugs/10.20261006__58___addurl__58___git-annex_died_of_signal_4/) I can report that HomeBrew have updated their build options to no longer pin a pre-release package, and seems to be back to building against an older `crypton` without the problem.  So that's encouraging!)

todo
diff --git a/doc/todo/git-annex_transfers_better_progress.mdwn b/doc/todo/git-annex_transfers_better_progress.mdwn
index 24cccbba71..69ecde09df 100644
--- a/doc/todo/git-annex_transfers_better_progress.mdwn
+++ b/doc/todo/git-annex_transfers_better_progress.mdwn
@@ -9,4 +9,11 @@ I think that both of these could be handled better by making progress updates
 for send/receive also update the transfer info file with the current byte
 count. This would mean writing some data to disk, but well, during a
 transfer a lot of data is already going to/from disk, so updating the file
-once per second would probably be unnoticable. --[[Joey]]
+once per second would probably be unnoticable.
+
+Seems that readTransferInfo never populates the bytesComplete field even if
+it got written. And while mkProgressUpdater should update the file,
+it seems it's not?
+
+I have some work toward this in the `transferprogress` branch.
+--[[Joey]]

todo
diff --git a/doc/todo/git-annex_transfers_better_progress.mdwn b/doc/todo/git-annex_transfers_better_progress.mdwn
new file mode 100644
index 0000000000..24cccbba71
--- /dev/null
+++ b/doc/todo/git-annex_transfers_better_progress.mdwn
@@ -0,0 +1,12 @@
+`git-annex transfers` cannot display progress bars when files are being
+sent.
+
+Also, it cannot always display progress bars when files are being received,
+depending on the type of special remote. It can only notice when the object
+file is growing in size.
+
+I think that both of these could be handled better by making progress updates
+for send/receive also update the transfer info file with the current byte
+count. This would mean writing some data to disk, but well, during a
+transfer a lot of data is already going to/from disk, so updating the file
+once per second would probably be unnoticable. --[[Joey]]

wording
diff --git a/doc/git-annex-transfers.mdwn b/doc/git-annex-transfers.mdwn
index ba22970da8..ef2bb79486 100644
--- a/doc/git-annex-transfers.mdwn
+++ b/doc/git-annex-transfers.mdwn
@@ -8,8 +8,7 @@ git annex transfers
 
 # DESCRIPTION
 
-Displays a live view of files that are being transferred from and to
-remotes.
+A live view of files that are being transferred from and to remotes.
 
 For files that are being received, this often includes a progress meter.
 No progress information is available for files that are being sent.

todo
diff --git a/doc/todo/git-annex-transfers_inotify.mdwn b/doc/todo/git-annex-transfers_inotify.mdwn
new file mode 100644
index 0000000000..e3536b9604
--- /dev/null
+++ b/doc/todo/git-annex-transfers_inotify.mdwn
@@ -0,0 +1,3 @@
+Currently it uses a 1 second sleep loop and re-parses every transfer log
+each time through. Could inotify on the transfer log directory, which would
+use less CPU and notice changes faster. --[[Joey]]

close
diff --git a/doc/todo/transfers_command.mdwn b/doc/todo/transfers_command.mdwn
index e25d03898f..c3be6e5b62 100644
--- a/doc/todo/transfers_command.mdwn
+++ b/doc/todo/transfers_command.mdwn
@@ -1,2 +1,4 @@
 The `git-annex-transfers` branch has a new command, not quite ready for
 merging. --[[Joey]]
+
+> [[done]] 

implement --localfilenames
The updateDatabase up front is to avoid multiple threads from each
trying to update it, which will make threads that are unable to start
the update display keys until it gets updated.
diff --git a/Command/Transfers.hs b/Command/Transfers.hs
index 0947368d60..aeb4a650ef 100644
--- a/Command/Transfers.hs
+++ b/Command/Transfers.hs
@@ -17,6 +17,8 @@ import Messages.Progress
 import Messages.Concurrent
 import Utility.ThreadScheduler
 import Annex.Concurrent
+import Database.Keys
+import Git.FilePath
 
 import qualified Data.Map as M
 import Control.Concurrent.STM
@@ -26,10 +28,24 @@ cmd :: Command
 cmd = noCommit $ withAnnexOptions [jsonOptions, jsonProgressOption] $
 	command "transfers" SectionQuery
 		"display transfers"
-		paramNothing (withParams (withNothing seek))
+		paramNothing (seek <$$> optParser)
 
-seek :: CommandSeek
-seek = withConcurrentOutput $ do
+data TransfersOptions = TransfersOptions
+	{ localFilenames :: Bool 
+	}
+
+optParser :: CmdParamsDesc -> Parser TransfersOptions
+optParser _ = TransfersOptions
+	<$> switch
+		( long "localfilenames"
+		<> short 'l'
+		<> help "display local filenames, not remote filenames"
+		)
+
+seek :: TransfersOptions -> CommandSeek
+seek o = withConcurrentOutput $ do
+	when (localFilenames o)
+		updateDatabase
 	rs <- remoteList
 	let remotemap = M.fromList $ zip (map uuid rs) rs
 	go remotemap M.empty
@@ -38,15 +54,7 @@ seek = withConcurrentOutput $ do
 		l <- getTransfers
 		new <- forM l $ \(t, ti) -> do
 			case M.lookup t transfermap of
-				Nothing -> do
-					endvar <- liftIO newEmptyTMVarIO
-					a <- forkState $ 
-						withMessageState $ \s ->
-							inOwnConsoleRegion s $
-								includeCommandAction $
-									start t ti endvar remotemap
-					tid <- liftIO $ async a
-					return (t, (endvar, tid))
+				Nothing -> startone remotemap t ti
 				Just v -> return (t, v)
 		let newmap = M.fromList new
 		forM_ (M.toList transfermap) $ \(t, (endvar, tid)) -> 
@@ -55,23 +63,40 @@ seek = withConcurrentOutput $ do
 				void $ join $ liftIO $ wait tid
 		liftIO $ threadDelaySeconds (SecondsDelay 1)
 		go remotemap newmap
+	
+	startone remotemap t ti = do
+		endvar <- liftIO newEmptyTMVarIO
+		a <- forkState $ 
+			withMessageState $ \s ->
+				inOwnConsoleRegion s $
+					includeCommandAction $
+						start o t ti endvar remotemap
+		tid <- liftIO $ async a
+		return (t, (endvar, tid))
 
-start :: Transfer -> TransferInfo -> TMVar () -> M.Map UUID Remote -> CommandStart
-start t ti endvar remotemap = startingNoMessage ai $ do
-	showStartMessage (StartMessage verb ai si)
-	case M.lookup (transferUUID t) remotemap of
-		Just r -> showNote $ UnquotedString $ fromto ++ " " ++ name r
-		Nothing -> noop
-	case transferDirection t of
-		Download -> do
-			o <- fromRepo $ gitAnnexTmpObjectLocation k
-			meteredFile o Nothing k $ const waitend
-		Upload -> waitend
-	showEnd "done"
-	next $ return True
+start :: TransfersOptions -> Transfer -> TransferInfo -> TMVar () -> M.Map UUID Remote -> CommandStart
+start o t ti endvar remotemap = do
+	af <- if localFilenames o
+		then headMaybe <$> getAssociatedFiles k >>= \case
+			Just topf -> relaf =<< fromRepo (fromTopFilePath topf)
+			Nothing -> pure $ AssociatedFile Nothing
+		else pure $ associatedFile ti
+	let ai = ActionItemAssociatedFile af k
+	startingNoMessage ai $ do
+		showStartMessage (StartMessage verb ai si)
+		case M.lookup (transferUUID t) remotemap of
+			Just r -> showNote $ UnquotedString $
+				fromto ++ " " ++ name r
+			Nothing -> noop
+		case transferDirection t of
+			Download -> do
+				obj <- fromRepo $ gitAnnexTmpObjectLocation k
+				meteredFile obj Nothing k $ const waitend
+			Upload -> waitend
+		showEnd "done"
+		next $ return True
   where
   	k = transferKey t
-	ai = ActionItemAssociatedFile (associatedFile ti) k
 	si = SeekInput []
   	verb = case transferDirection t of
 		Upload -> "send"
@@ -81,3 +106,4 @@ start t ti endvar remotemap = startingNoMessage ai $ do
 		Download -> "from"
 	waitend = 
 		liftIO $ atomically $ takeTMVar endvar
+	relaf f = AssociatedFile . Just <$> liftIO (relPathCwdToFile f)
diff --git a/doc/git-annex-transfers.mdwn b/doc/git-annex-transfers.mdwn
index 7f084c6258..ba22970da8 100644
--- a/doc/git-annex-transfers.mdwn
+++ b/doc/git-annex-transfers.mdwn
@@ -28,16 +28,7 @@ control-c.
 
 # OPTIONS
 
-* `--json`
-
-  Enable JSON output. This is intended to be parsed by programs that use
-  git-annex. Each line of output is a JSON object.
-
-* `--json-progress`
-
-  Include progress objects in JSON output.
-
-* `--localfilenames`
+* `--localfilenames`, '-l'
 
   Usually, the filename displayed by this command is the name provided
   by the initiator of the transfer. So it may not correspond to the name
@@ -52,6 +43,18 @@ control-c.
   that corresponds to the git-annex key. If there is no such
   file, the key will be shown instead.
 
+* `--json`
+
+  Enable JSON output. This is intended to be parsed by programs that use
+  git-annex. Each line of output is a JSON object.
+
+  Note that display of these objects is deferred until a transfer ends.
+  To get currently running transfers, combine this with `--json-progeess`.
+
+* `--json-progress`
+
+  Include progress objects in JSON output.
+
 * Also the [[git-annex-common-options]](1) can be used.
 
 # SEE ALSO

reminder
diff --git a/doc/todo/transfers_command.mdwn b/doc/todo/transfers_command.mdwn
new file mode 100644
index 0000000000..e25d03898f
--- /dev/null
+++ b/doc/todo/transfers_command.mdwn
@@ -0,0 +1,2 @@
+The `git-annex-transfers` branch has a new command, not quite ready for
+merging. --[[Joey]]

transfers command
--localfilenames is not yet implemented
Currently needs to be run with a large -J number, so that it has enough
jobs to display all active transfers. This is suboptimal, and not
documented. There needs to be a way to let it use an unbounded number
of jobs. That will need changes to Annex.WorkerPool.
diff --git a/CHANGELOG b/CHANGELOG
index b9a6da7f74..5a9b8a7d2e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,7 +9,7 @@ git-annex (10.20261027) UNRELEASED; urgency=medium
   * Fix bug in parsing the P2P protocol that caused breakage when filenames
     contained unicode multibyte space characters.
   * Fix buggy handling of a submodule in a linked git worktree.
-  * Added transfers command, displays current transfers.
+  * Added transfers command, displays a live view of transfers.
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/CmdLine/GitAnnex.hs b/CmdLine/GitAnnex.hs
index fbb51ea79e..3393ca1e4d 100644
--- a/CmdLine/GitAnnex.hs
+++ b/CmdLine/GitAnnex.hs
@@ -39,6 +39,7 @@ import qualified Command.SetKey
 import qualified Command.DropKey
 import qualified Command.Transferrer
 import qualified Command.TransferKey
+import qualified Command.Transfers
 import qualified Command.SetPresentKey
 import qualified Command.ReadPresentKey
 import qualified Command.CheckPresentKey
@@ -216,6 +217,7 @@ cmds testoptparser testrunner mkbenchmarkgenerator = map addGitAnnexCommonOption
 	, Command.DropKey.cmd
 	, Command.Transferrer.cmd
 	, Command.TransferKey.cmd
+	, Command.Transfers.cmd
 	, Command.SetPresentKey.cmd
 	, Command.ReadPresentKey.cmd
 	, Command.CheckPresentKey.cmd
diff --git a/Command/Transfers.hs b/Command/Transfers.hs
new file mode 100644
index 0000000000..9406afde44
--- /dev/null
+++ b/Command/Transfers.hs
@@ -0,0 +1,75 @@
+{- git-annex command
+ -
+ - Copyright 2026 Joey Hess <id@joeyh.name>
+ -
+ - Licensed under the GNU AGPL version 3 or higher.
+ -}
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Command.Transfers where
+
+import Command
+import Types.Transfer
+import Logs.Transfer
+import Remote
+import Messages.Progress
+import Utility.ThreadScheduler
+
+import qualified Data.Map as M
+import Control.Concurrent.STM
+
+cmd :: Command
+cmd = noCommit $ withAnnexOptions [jsonOptions, jsonProgressOption, jobsOption] $
+	command "transfers" SectionQuery
+		"display transfers"
+		paramNothing (withParams (withNothing seek))
+
+seek :: CommandSeek
+seek = startConcurrency commandStages $ do
+	rs <- remoteList
+	let remotemap = M.fromList $ zip (map uuid rs) rs
+	go remotemap M.empty
+  where
+	go remotemap transfermap = do
+		l <- getTransfers
+		new <- forM l $ \(t, ti) -> do
+			case M.lookup t transfermap of
+				Nothing -> do
+					endvar <- liftIO newEmptyTMVarIO
+					commandAction $
+						start t ti endvar remotemap
+					return (t, endvar)
+				Just endvar -> return (t, endvar)
+		let newmap = M.fromList new
+		forM_ (M.toList transfermap) $ \(t, endvar) -> 
+			unless (M.member t newmap) $
+				liftIO $ atomically $ putTMVar endvar ()
+		liftIO $ threadDelaySeconds (SecondsDelay 1)
+		go remotemap newmap
+
+start :: Transfer -> TransferInfo -> TMVar () -> M.Map UUID Remote -> CommandStart
+start t ti endvar remotemap = startingNoMessage ai $ do
+	showStartMessage (StartMessage verb ai si)
+	case M.lookup (transferUUID t) remotemap of
+		Just r -> showNote $ UnquotedString $ fromto ++ " " ++ name r
+		Nothing -> noop
+	case transferDirection t of
+		Download -> do
+			o <- fromRepo $ gitAnnexTmpObjectLocation k
+			meteredFile o Nothing k $ const waitend
+		Upload -> waitend
+	showEnd "done"
+	next $ return True
+  where
+  	k = transferKey t
+	ai = ActionItemAssociatedFile (associatedFile ti) k
+	si = SeekInput []
+  	verb = case transferDirection t of
+		Upload -> "send"
+		Download -> "receive"
+	fromto = case transferDirection t of
+		Upload -> "to"
+		Download -> "from"
+	waitend = 
+		liftIO $ atomically $ takeTMVar endvar
diff --git a/Messages.hs b/Messages.hs
index 34d1f99170..8b15888db3 100644
--- a/Messages.hs
+++ b/Messages.hs
@@ -23,6 +23,7 @@ module Messages (
 	showOutput,
 	showLongNote,
 	showInfo,
+	showEnd,
 	showEndOk,
 	showEndFail,
 	showEndResult,
@@ -175,6 +176,9 @@ formatLongNote s = "\n" <> indent s <> "\n"
 showInfo :: StringContainingQuotedPath -> Annex ()
 showInfo s = outputMessage' outputJSON (JSON.info (decodeBS (noquote s))) formatLongNote s
 
+showEnd :: StringContainingQuotedPath -> Annex ()
+showEnd s = outputMessage JSON.end' id (s <> "\n")
+
 showEndOk :: Annex ()
 showEndOk = showEndResult True
 
diff --git a/Messages/JSON.hs b/Messages/JSON.hs
index 4b7284173e..1a8f013280 100644
--- a/Messages/JSON.hs
+++ b/Messages/JSON.hs
@@ -16,6 +16,7 @@ module Messages.JSON (
 	start,
 	startActionItem,
 	end,
+	end',
 	finalize,
 	addErrorMessage,
 	note,
@@ -106,6 +107,10 @@ end :: Bool -> JSONBuilder
 end b (Just (o, _)) = Just (HM.insert "success" (toJSON' b) o, True)
 end _ Nothing = Nothing
 
+end' :: JSONBuilder
+end' (Just (o, _)) = Just (o, True)
+end' Nothing = Nothing
+
 -- Always include error-messages field, even if empty,
 -- to make the json be self-documenting.
 finalize :: Object -> Object
diff --git a/Messages/Progress.hs b/Messages/Progress.hs
index 3e2e843494..78e7d5f75b 100644
--- a/Messages/Progress.hs
+++ b/Messages/Progress.hs
@@ -175,7 +175,7 @@ metered' st setclear othermeterupdate msize bwlimit showoutput a = go st
 
 	minratelimit = min consoleratelimit jsonratelimit
 		
-{- Poll file size to display meter. -}
+{- Poll file size to display meter while running an action. -}
 meteredFile
 	:: MeterSize sizer
 	=> OsPath
diff --git a/doc/git-annex-transfers.mdwn b/doc/git-annex-transfers.mdwn
index 31857caba6..7f084c6258 100644
--- a/doc/git-annex-transfers.mdwn
+++ b/doc/git-annex-transfers.mdwn
@@ -8,40 +8,39 @@ git annex transfers
 
 # DESCRIPTION
 
-Lists files that are being sent to remotes or recieved from remotes
-by any other git-annex process running in this repository.
+Displays a live view of files that are being transferred from and to
+remotes.
 
-The default output from this command is one line per file, like this:
+For files that are being received, this often includes a progress meter.
+No progress information is available for files that are being sent.
 
-    uploading foo (to darkstar) 50%
-    downloading bar (from darkstar) 1%
+For example:
 
-# OPTIONS
-
-* `--live`
-
-  Keep running and display transfers as they happen, with new
-  lines being added when new transfers start, and each line being
-  updated as the transfer proceeds.

(Diff truncated)
update
diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn
index 06ef9674d6..865d04a0a5 100644
--- a/doc/git-annex.mdwn
+++ b/doc/git-annex.mdwn
@@ -575,6 +575,12 @@ content from the key-value store.
 
   See [[git-annex-inprogress]](1) for details.
 
+* `transfers
+
+  Display transfers.
+
+  See [[git-annex-transfers]](1) for details.
+
 * `findkeys`
 
   Similar to `git-annex find`, but operating on keys.

update
diff --git a/doc/todo/remove_dependency_on_crypon_and_tls.mdwn b/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
index 2ff68c829b..426b578dd6 100644
--- a/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
+++ b/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
@@ -27,6 +27,8 @@ Here is a plan:
   flag, but it would be easier if botan bundled the C library in the
   haskell package. Which they have considered doing.  
   The `botan-default` branch makes the switch.
+  (Waiting on a fix for https://github.com/joeyh/git-annex-wheel/
+  test failure.)
 * botan lacks skein and blake2, so would need to get those hashes another
   way
 * Switch from http-client-tls to http-client-openssl.

comment
diff --git a/doc/todo/remove_webapp/comment_6_266addf5977e7900f48933e6c2f87206._comment b/doc/todo/remove_webapp/comment_6_266addf5977e7900f48933e6c2f87206._comment
new file mode 100644
index 0000000000..3b8c8fc4a7
--- /dev/null
+++ b/doc/todo/remove_webapp/comment_6_266addf5977e7900f48933e6c2f87206._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 6"""
+ date="2026-11-01T16:14:18Z"
+ content="""
+The new [[git-annex-transfers]] is a more targeted take on `git-annex
+activity`.
+"""]]

man page for git-annex transfers
not yet implemented
diff --git a/CHANGELOG b/CHANGELOG
index b714896fb3..b9a6da7f74 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,7 @@ git-annex (10.20261027) UNRELEASED; urgency=medium
   * Fix bug in parsing the P2P protocol that caused breakage when filenames
     contained unicode multibyte space characters.
   * Fix buggy handling of a submodule in a linked git worktree.
+  * Added transfers command, displays current transfers.
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/doc/git-annex-inprogress.mdwn b/doc/git-annex-inprogress.mdwn
index 744cebf84d..12940589f0 100644
--- a/doc/git-annex-inprogress.mdwn
+++ b/doc/git-annex-inprogress.mdwn
@@ -65,6 +65,8 @@ the exit status will be 1.
 
 [[git-annex-get]](1)
 
+[[git-annex-transfers]](1)
+
 # AUTHOR
 
 Joey Hess <id@joeyh.name>
diff --git a/doc/git-annex-transfers.mdwn b/doc/git-annex-transfers.mdwn
new file mode 100644
index 0000000000..31857caba6
--- /dev/null
+++ b/doc/git-annex-transfers.mdwn
@@ -0,0 +1,68 @@
+# NAME
+
+git-annex transfers - display transfers
+
+# SYNOPSIS
+
+git annex transfers
+
+# DESCRIPTION
+
+Lists files that are being sent to remotes or recieved from remotes
+by any other git-annex process running in this repository.
+
+The default output from this command is one line per file, like this:
+
+    uploading foo (to darkstar) 50%
+    downloading bar (from darkstar) 1%
+
+# OPTIONS
+
+* `--live`
+
+  Keep running and display transfers as they happen, with new
+  lines being added when new transfers start, and each line being
+  updated as the transfer proceeds.
+
+  When a transfer ends, its line is updated to indicate that,
+  by changing to start with "uploaded" or "downloaded". (This does
+  not necessarily mean that the transfer succeeded.)
+
+* `--stream`
+
+  Like `--live` but displays a new line each time there is an update
+  in the status of a transfer. This makes it parseable.
+
+* `--json`
+
+  Enable JSON output. This is intended to be parsed by programs that use
+  git-annex. Each line of output is a JSON object.
+
+* `--localfilenames`
+
+  Usually, the filename displayed by this command is the name provided
+  by the initiator of the transfer. So it may not correspond with the name
+  of the file in the currently checked out branch. For example, when 
+  git-annex-shell is sending a file from a server to a client, the client
+  could have a branch checked out that uses a different name for the file.
+  Some filenames also need to be mangled for security reasons, or reasons
+  of backwards compatability with older versions of git-annex.
+
+  With this option, rather than displaying the filename provided with the
+  transfer, display the name of a file in the locally checked out branch
+  that corresponds to the git-annex key. If there is no such
+  file, the key will be shown instead.
+
+* Also the [[git-annex-common-options]](1) can be used.
+
+# SEE ALSO
+
+[[git-annex]](1)
+
+[[git-annex-inprogress]](1)
+
+# AUTHOR
+
+Joey Hess <id@joeyh.name>
+
+Warning: Automatically converted into a man page by mdwn2man. Edit with care.

botan by default unblocked
diff --git a/doc/todo/remove_dependency_on_crypon_and_tls.mdwn b/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
index bea854ce1b..2ff68c829b 100644
--- a/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
+++ b/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
@@ -26,11 +26,7 @@ Here is a plan:
 * Switch git-annex to botan for hash functions. There is already a build
   flag, but it would be easier if botan bundled the C library in the
   haskell package. Which they have considered doing.  
-  The `botan-default` branch makes the switch, but is blocked on
-  getting the windows build to work with botan. 
-  (I have wasted about 2 days on that so far, it seems
-  like a real toolchain nightmare. See gitannexbuilder github branch 
-  for the gory details.)
+  The `botan-default` branch makes the switch.
 * botan lacks skein and blake2, so would need to get those hashes another
   way
 * Switch from http-client-tls to http-client-openssl.

Added a comment
diff --git a/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree/comment_3_4ff0edbef88fbbf1ab4623905c7992ca._comment b/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree/comment_3_4ff0edbef88fbbf1ab4623905c7992ca._comment
new file mode 100644
index 0000000000..cac3410e02
--- /dev/null
+++ b/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree/comment_3_4ff0edbef88fbbf1ab4623905c7992ca._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="leej3"
+ avatar="http://cdn.libravatar.org/avatar/da183043eb69dbc4fcd5a45ac828ee0c"
+ subject="comment 3"
+ date="2026-10-30T18:49:56Z"
+ content="""
+Sounds good. Thanks!
+"""]]

Fix buggy handling of a submodule in a linked git worktree
diff --git a/CHANGELOG b/CHANGELOG
index fa47ee9724..b714896fb3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,6 +8,7 @@ git-annex (10.20261027) UNRELEASED; urgency=medium
     in non-unicode locales.
   * Fix bug in parsing the P2P protocol that caused breakage when filenames
     contained unicode multibyte space characters.
+  * Fix buggy handling of a submodule in a linked git worktree.
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/Git/Config.hs b/Git/Config.hs
index 4e72b73be6..17cad284c7 100644
--- a/Git/Config.hs
+++ b/Git/Config.hs
@@ -1,6 +1,6 @@
 {- git repository configuration handling
  -
- - Copyright 2010-2022 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -24,6 +24,7 @@ import qualified Git.Construct
 import Utility.UserInfo
 import Utility.Process.Transcript
 import Utility.Debug
+import qualified Utility.RawFilePath as R
 
 {- Returns a single git config setting, or a fallback value if not set. -}
 get :: ConfigKey -> ConfigValue -> Repo -> ConfigValue
@@ -181,12 +182,20 @@ updateLocation' r l@(Local {}) = do
 	l' <- case getMaybe "core.worktree" r of
 		Nothing -> return l
 		Just (ConfigValue d) -> do
-			{- core.worktree is relative to the gitdir -}
-			top <- absPath (gitdir l)
+			top <- gitdirtop
 			let p = absPathFrom top (toOsPath d)
 			return $ l { worktree = Just p }
 		Just NoConfigValue -> return l
 	return $ r { location = l' }
+  where
+	{- core.worktree is relative to the gitdir. 
+	 - When the gitdir is a symlink, it's relative to the destination
+	 - of the symlink.
+	 -}
+	gitdirtop = catchMaybeIO (R.readSymbolicLink (fromOsPath (gitdir l))) >>= \case
+		Nothing -> absPath (gitdir l)
+		Just linktarget -> absPath $
+			takeDirectory (gitdir l) </> toOsPath linktarget
 updateLocation' r l = return r { location = l }
 
 data ConfigStyle = ConfigList | ConfigNullList
diff --git a/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree.mdwn b/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree.mdwn
index 866d9d9088..90a437d609 100644
--- a/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree.mdwn
+++ b/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree.mdwn
@@ -62,3 +62,4 @@ I have tried it across a range of versions.
 
 Yes, lots. It's been working well for me in keeping track of scientific analyses.
 
+> [[fixed|done]] --[[Joey]]
diff --git a/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree/comment_2_24cedc63f261044299a6723f58374747._comment b/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree/comment_2_24cedc63f261044299a6723f58374747._comment
new file mode 100644
index 0000000000..f3154a0147
--- /dev/null
+++ b/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree/comment_2_24cedc63f261044299a6723f58374747._comment
@@ -0,0 +1,19 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 2"""
+ date="2026-10-30T14:34:26Z"
+ content="""
+As far as I can see, core.worktree is the only config setting that
+is relative to the path of the gitdir.
+
+Changing the gitdir to be where the symlink points to does not seem like a
+good idea, it would affect git-annex code that creates symlinks pointing
+into the gitdir.
+
+Also, `git rev-parse --git-dir` is just ".git" in this situation,
+so even git internally does not follow the symlink manually, except
+presumably when resolving core.worktree.
+
+So, I've fixed this only in the core.worktree handling code in
+updateLocation.
+"""]]

close
diff --git a/doc/bugs/10.20261006__58___addurl__58___git-annex_died_of_signal_4.mdwn b/doc/bugs/10.20261006__58___addurl__58___git-annex_died_of_signal_4.mdwn
index 1841a805e7..f0b61e5465 100644
--- a/doc/bugs/10.20261006__58___addurl__58___git-annex_died_of_signal_4.mdwn
+++ b/doc/bugs/10.20261006__58___addurl__58___git-annex_died_of_signal_4.mdwn
@@ -88,3 +88,5 @@ ewen@basadi:/annex/downloaded/apple/macos/macOS_13-Ventura$
 ### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
 
 Yes, git-annex was working fine for both "addurl" and "importfeed" on the September 2026 release, built in a similar manner (local build from Homebrew Formula).  Thanks, it's been very helpful.
+
+> [[done]], and see [[todo/remove_dependency_on_crypon_and_tls]]. --[[Joey]]

Added a comment
diff --git a/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes/comment_2_668fa983e371ed70bd78371a0e64bc67._comment b/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes/comment_2_668fa983e371ed70bd78371a0e64bc67._comment
new file mode 100644
index 0000000000..1796e1c641
--- /dev/null
+++ b/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes/comment_2_668fa983e371ed70bd78371a0e64bc67._comment
@@ -0,0 +1,12 @@
+[[!comment format=mdwn
+ username="matrss"
+ avatar="http://cdn.libravatar.org/avatar/cd1c0b3be1af288012e49197918395f0"
+ subject="comment 2"
+ date="2026-10-30T10:00:06Z"
+ content="""
+> It does work for special remotes, but not for git remotes.
+
+Ah, well that explains why we saw different behavior :)
+
+Just for the record, I don't currently have a use-case for proxies, and the one I have in mind for the future would only involve a special remote that is read-only by itself already, so I don't see any urgency for this issue. Still, good to have it logged.
+"""]]

analysis
diff --git a/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes/comment_1_1d0142f161d6e0f312e445c395a2fd51._comment b/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes/comment_1_1d0142f161d6e0f312e445c395a2fd51._comment
new file mode 100644
index 0000000000..3256eab947
--- /dev/null
+++ b/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes/comment_1_1d0142f161d6e0f312e445c395a2fd51._comment
@@ -0,0 +1,21 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2026-10-30T06:33:34Z"
+ content="""
+It does work for special remotes, but not for git remotes.
+
+I think the reason for the difference is that Annex.Proxy has
+proxyGitRemoteSide and proxySpecialRemoteSide. The former opens a P2P
+connection to the proxied remote's git-annex-shell, and so it does not
+access the remote via a Remote object like the latter does. So the fact
+that the Remote has methods that have been made readonly has no effect.
+
+So probably proxySpecialRemoteSide needs to look at annex-readonly and then
+somehow limit the P2P connection to ServeReadOnly. But I don't think there
+is actually any way to do that, short of modifying git-annex-shell or
+somehow injecting `GIT_ANNEX_SHELL_READONLY` into its environment when
+running it. So supporting it might involve consuming the P2P protocol
+and filtering out writes before relaying it. Unfortunately, that would
+add copying of DATA, so could be a significant slowdown.
+"""]]

another one
diff --git a/doc/no_llm_code.mdwn b/doc/no_llm_code.mdwn
index 35a415330e..d24c258839 100644
--- a/doc/no_llm_code.mdwn
+++ b/doc/no_llm_code.mdwn
@@ -136,6 +136,10 @@ including `tls`.
 
 git-annex supports being built with older versions.
 
+### http-semantics
+
+TODO
+
 ### Cabal
 
 [First LLM generated code](https://github.com/haskell/cabal/commit/da8b314563feb15a3df7bc1baeef4b7aa08f7578)

diff --git a/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes.mdwn b/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes.mdwn
index 3d98da0969..0f37021928 100644
--- a/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes.mdwn
+++ b/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes.mdwn
@@ -150,3 +150,6 @@ ok
 """]]
 
 ### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)
+
+
+[[!tag projects/ICE4]]

diff --git a/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes.mdwn b/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes.mdwn
new file mode 100644
index 0000000000..3d98da0969
--- /dev/null
+++ b/doc/bugs/annex-readonly_does_not_apply_to_proxied_remotes.mdwn
@@ -0,0 +1,152 @@
+### Please describe the problem in your own words.
+
+The `remote.proxy-target.annex-readonly` configuration does not prevent modifications to the proxy-target remote if it is accessed via a proxy.
+
+### What steps will reproduce the problem?
+
+[[!format sh """
+#!/usr/bin/env bash
+set -euxo pipefail
+
+# Set up an empty proxy target repository
+mkdir target
+cd target
+git init
+git annex init
+cd ..
+
+# Set up the proxying repository
+mkdir proxy
+cd proxy
+git init
+git annex init
+echo test0 > test0.txt
+git annex add .
+git commit -m commit
+# Add proxy target remote, make it readonly, and enable proxying
+git remote add target ../target
+git config remote.target.annex-readonly true
+git config remote.target.annex-proxy true
+git annex updateproxy
+# Check that the readonly configuration is effective
+set +e
+git annex move --to target
+set -e
+cd ..
+
+# Make a clone of the proxying repository
+git clone ssh://$USER@localhost$PWD/proxy clone
+cd clone
+git annex init
+# Add some new annexed content
+echo test1 > test1.txt
+echo test2 > test2.txt
+git annex add .
+git commit -m commit2
+# Modify the proxy target, even though it is configured to be readonly
+git annex move --to origin-target
+git annex move --from origin-target
+cd ..
+"""]]
+
+### What version of git-annex are you using? On what operating system?
+
+```
+$ git annex version
+git-annex version: 10.20261026-g351f69fa638bde0d2f68ee5b8bab109c75945331
+build flags: Assistant Webapp Inotify TorrentParser MagicMime Benchmark Feeds Testsuite S3 WebDAV Servant OsPath Blake3 XXH3 TastyTap
+dependency versions: aws-0.25.2 bloomfilter-2.0.1.3 crypton-1.0.6 DAV-1.3.4 feed-1.3.2.1 ghc-9.10.3 http-client-0.7.19 torrent-10000.1.3 uuid-1.3.16.1 yesod-1.6.2.3
+key/value backends: SHA256E SHA256 SHA512E SHA512 SHA224E SHA224 SHA384E SHA384 SHA3_256E SHA3_256 SHA3_512E SHA3_512 SHA3_224E SHA3_224 SHA3_384E SHA3_384 SKEIN256E SKEIN256 SKEIN512E SKEIN512 BLAKE2B256E BLAKE2B256 BLAKE2B512E BLAKE2B512 BLAKE2B160E BLAKE2B160 BLAKE2B224E BLAKE2B224 BLAKE2B384E BLAKE2B384 BLAKE2BP512E BLAKE2BP512 BLAKE2S256E BLAKE2S256 BLAKE2S160E BLAKE2S160 BLAKE2S224E BLAKE2S224 BLAKE2SP256E BLAKE2SP256 BLAKE2SP224E BLAKE2SP224 BLAKE3_256E BLAKE3_256 XXH3E XXH3 SHA1E SHA1 MD5E MD5 WORM URL GITBUNDLE GITMANIFEST VURL X*
+remote types: git gcrypt p2p S3 bup directory rsync web bittorrent webdav adb tahoe glacier ddar git-lfs httpalso borg rclone hook external compute mask
+operating system: linux x86_64
+supported repository versions: 8 9 10
+upgrade supported from repository versions: 0 1 2 3 4 5 6 7 8 9 10
+```
+
+### Please provide any additional information below.
+
+[[!format sh """
+# If you can, paste a complete transcript of the problem occurring here.
+# If the problem is with the git-annex assistant, paste in .git/annex/daemon.log
+
+$ ./reproducer.bash 
++ mkdir target
++ cd target
++ git init
+Leeres Git-Repository in /home/icg149/Playground/proxy-readonly/target/.git/ initialisiert
++ git annex init
+init  ok
+(recording state in git...)
++ cd ..
++ mkdir proxy
++ cd proxy
++ git init
+Leeres Git-Repository in /home/icg149/Playground/proxy-readonly/proxy/.git/ initialisiert
++ git annex init
+init  ok
+(recording state in git...)
++ echo test0
++ git annex add .
+add test0.txt 
+ok                                
+(recording state in git...)
++ git commit -m commit
+[main (Root-Commit) 27cd0ae] commit
+ 1 file changed, 1 insertion(+)
+ create mode 120000 test0.txt
++ git remote add target ../target
++ git config remote.target.annex-readonly true
++ git config remote.target.annex-proxy true
++ git annex updateproxy
+Started proxying for target
++ set +e
++ git annex move --to target
+move test0.txt (to target...) 
+  this remote is readonly
+failed
+move: 1 failed
++ set -e
++ cd ..
++ git clone ssh://icg149@localhost/home/icg149/Playground/proxy-readonly/proxy clone
+Klone nach 'clone'...
+remote: Objekte aufzählen: 16, fertig.
+remote: Zähle Objekte: 100% (16/16), fertig.
+remote: Komprimiere Objekte: 100% (12/12), fertig.
+remote: Gesamt 16 (Delta 2), Wiederverwendet 0 (Delta 0), Paket wiederverwendet 0 (von 0)
+Empfange Objekte: 100% (16/16), fertig.
+Löse Unterschiede auf: 100% (2/2), fertig.
++ cd clone
++ git annex init
+init  ok
+(recording state in git...)
++ echo test1
++ echo test2
++ git annex add .
+add test1.txt 
+ok                                
+add test2.txt 
+ok                                
+(recording state in git...)
++ git commit -m commit2
+[main 6a46897] commit2
+ 2 files changed, 2 insertions(+)
+ create mode 120000 test1.txt
+ create mode 120000 test2.txt
++ git annex move --to origin-target
+move test1.txt (to origin-target...) 
+ok                                
+move test2.txt (to origin-target...) 
+ok                                
+(recording state in git...)
++ git annex move --from origin-target
+move test1.txt (from origin-target...) 
+ok                                
+move test2.txt (from origin-target...) 
+ok                                
+(recording state in git...)
++ cd ..
+
+# End of transcript or log.
+"""]]
+
+### Have you had any luck using git-annex before? (Sometimes we get tired of reading bug reports all day and a lil' positive end note does wonders)

update
diff --git a/doc/todo/remove_dependency_on_crypon_and_tls.mdwn b/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
index d0ae5744f5..bea854ce1b 100644
--- a/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
+++ b/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
@@ -26,10 +26,11 @@ Here is a plan:
 * Switch git-annex to botan for hash functions. There is already a build
   flag, but it would be easier if botan bundled the C library in the
   haskell package. Which they have considered doing.  
-  The `botan-default` branch makes the switch, but is blocked on needing
-  a way to install botan on windows. 
-  [cross compiling with MinGW](https://botan.randombit.net/handbook/building.html#cross-compiling)
-  may be needed, unless a prebuilt release can be found.
+  The `botan-default` branch makes the switch, but is blocked on
+  getting the windows build to work with botan. 
+  (I have wasted about 2 days on that so far, it seems
+  like a real toolchain nightmare. See gitannexbuilder github branch 
+  for the gory details.)
 * botan lacks skein and blake2, so would need to get those hashes another
   way
 * Switch from http-client-tls to http-client-openssl.

comment
diff --git a/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree/comment_1_f532e3ee98e21d37fe468a53d8ace300._comment b/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree/comment_1_f532e3ee98e21d37fe468a53d8ace300._comment
new file mode 100644
index 0000000000..cb3cc3914d
--- /dev/null
+++ b/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree/comment_1_f532e3ee98e21d37fe468a53d8ace300._comment
@@ -0,0 +1,43 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2026-10-29T20:50:56Z"
+ content="""
+This has nothing to do with `git-annex info`, any git-annex command after
+the first initialization behaves this way.
+
+It seems that git-annex is seeing the git config
+of "core.worktree=../../../../../../linked/sub" and getting a little bit
+confused. 
+
+It constructs a Repo with a location of eg
+
+	Local {gitdir = "/home/joey/tmp/tmp.rQ1fokJzh0/linked/sub/.git", worktree = Just "/home/linked/sub"}
+
+That worktree is wrong. And since it's not being run in that worktree,
+it tries to chdir to it.
+
+core.worktree is supposed to be relative to the gitdir. And
+since `.git -> ../../main/.git/worktrees/linked/modules/sub/`,
+that's "../../main/.git/worktrees/linked/modules/sub/../../../../../../linked/sub"
+aka "../../linked/sub".
+
+The wrong worktree comes from Git.Config.updateLocation. It is using the
+gitdir seen above, rather than following the symlink to the git directory it points to.
+So it uses "/home/joey/tmp/tmp.rQ1fokJzh0/linked/sub/.git/../../../../../../linked/sub"
+which reduces to "/home/linked/sub".
+
+So either Git.Config.updateLocation needs to change to follow the gitdir symlink,
+or the symlink needs to be followed at an earlier point.
+
+It's worth noting that the first time git-annex runs, it 
+ends up with the correct location eg
+
+	Local {gitdir = "/home/joey/tmp/tmp.D2wQYPhq9O/main/.git/worktrees/linked/modules/sub", worktree = Just "/home/joey/tmp/tmp.D2wQYPhq9O/linked/sub"}
+
+So the trouble comes from the various fixups that git-annex init has to do
+to convert .git files to symlinks.
+
+I will need to refresh my memory about linked worktrees and gitdir symlinks to get
+any further.
+"""]]

todo
diff --git a/doc/todo/p2phttp_--debug_--directory.mdwn b/doc/todo/p2phttp_--debug_--directory.mdwn
new file mode 100644
index 0000000000..915f2bea1c
--- /dev/null
+++ b/doc/todo/p2phttp_--debug_--directory.mdwn
@@ -0,0 +1,5 @@
+When using p2phttp --directory, --debug and other such flags have no effect.
+
+This should be possible to solve, by making `cmdnorepo` be passed
+an AnnexSetter. Then p2phttp could apply the settings of --debug and other
+such flags to each Annex. --[[Joey]]

todo
diff --git a/doc/todo/p2phttp_does_not_notice_newly_enabled_proxy.mdwn b/doc/todo/p2phttp_does_not_notice_newly_enabled_proxy.mdwn
new file mode 100644
index 0000000000..8563f906f1
--- /dev/null
+++ b/doc/todo/p2phttp_does_not_notice_newly_enabled_proxy.mdwn
@@ -0,0 +1,11 @@
+When a new proxy is configured in a repository, and p2phttp is serving that
+repository, it will not notice the new proxy, and so won't know how to
+serve its UUID, until it gets restarted.
+
+When serving a directory with multiple repositories, p2phttp is able to
+deal with a new repository being added. So maybe it also should be able to
+deal with a new proxy being configured? 
+
+This would entail making updateRepos scan for new proxies, basically.
+Then when a UUID that is not known is requested, it would rescan. Seems
+doable. --[[Joey]]

make ProtoAssociatedFile backwards compatible
This undoes b1cbcc7049ae7ebc68637455e2d23595ae801a17
-- make ProtoAssociatedFile round trip.
I hope one day to be able to revert this commit and get back to that
nice place. The comment explains when it might be possible to do so.
diff --git a/P2P/Protocol.hs b/P2P/Protocol.hs
index 0e3c298b5b..84d4426577 100644
--- a/P2P/Protocol.hs
+++ b/P2P/Protocol.hs
@@ -41,6 +41,7 @@ import System.Exit (ExitCode(..))
 import System.IO
 import qualified Data.ByteString.Lazy as L
 import qualified Data.Set as S
+import Data.Char
 import Data.Maybe
 import Data.Time.Clock.POSIX
 import Control.DeepSeq
@@ -228,20 +229,39 @@ instance Proto.Serializable Service where
 
 -- | Since ProtoAssociatedFile is not the last thing in a protocol line,
 -- its serialization cannot contain any whitespace. This is handled
--- by replacing whitespace with '%' (and '%' with '%%')
+-- by replacing ' ' with '%' (and '%' with '%%')
 --
 -- When deserializing an AssociatedFile from a peer, that escaping is
 -- reversed.
+--
+-- This does not currently round trip cleanly. That is done to avoid
+-- breaking backwards compatability with old versions of git-annex,
+-- where the P2P protocol split words on isSpace. Which is why this
+-- escapes '\t' and other spaces (including unicode spaces) to '%' as well.
+--
+-- Control characters are also sanitized. This sanitization is probably
+-- unncessary, since git-annex uses Utility.SafeOutput to avoid displaying
+-- control characters to the user.
+--
+-- Not roundtripping is ok because a ProtoAssociatedFile is normally
+-- only displayed to the user and so does not need to match a file on disk.
+-- (It may also be used in checking preferred content, which is very
+-- unlikely to care about spaces vs tabs or control characters.)
+--
+-- In the future, this could be made to round trip cleanly
+-- by only escaping ' ' and not sanitizing control characters.
+-- That should only be done once it's very unlikely that a peer
+-- will be running a version of git-annex older than 10.20261029.
 instance Proto.Serializable ProtoAssociatedFile where
 	serialize (ProtoAssociatedFile (AssociatedFile Nothing)) = ""
 	serialize (ProtoAssociatedFile (AssociatedFile (Just af))) = 
 		fromOsPath $ toInternalGitPath $
-			OS.concat $ map esc $ OS.unpack af
+			toOsPath $ concat $ map esc $ fromOsPath af
 	  where
-	  	esc c = case OS.toChar c of
-			'%' -> literalOsPath "%%"
-			' ' -> literalOsPath "%"
-			_ -> OS.singleton c
+	  	esc c
+			| c == '%' = "%%"
+			| isSpace c = "%"
+			| otherwise = [c]
 	
 	deserialize s = case fromInternalGitPath $ toOsPath $ deesc [] s of
 		f
@@ -254,7 +274,9 @@ instance Proto.Serializable ProtoAssociatedFile where
 	  	deesc b [] = reverse b
 		deesc b ('%':'%':cs) = deesc ('%':b) cs
 		deesc b ('%':cs) = deesc (' ':b) cs
-		deesc b (c:cs) = deesc (c:b) cs
+		deesc b (c:cs)
+			| isControl c = deesc ('_':b) cs
+			| otherwise = deesc (c:b) cs
 
 -- | Free monad for the protocol, combining net communication,
 -- and local actions.
diff --git a/doc/bugs/SSH_remote_get_fails_with_non-ASCII_whitespace/comment_1_4128646029ba561b47a6d872e1eadeb5._comment b/doc/bugs/SSH_remote_get_fails_with_non-ASCII_whitespace/comment_1_4128646029ba561b47a6d872e1eadeb5._comment
deleted file mode 100644
index c9a22b49a6..0000000000
--- a/doc/bugs/SSH_remote_get_fails_with_non-ASCII_whitespace/comment_1_4128646029ba561b47a6d872e1eadeb5._comment
+++ /dev/null
@@ -1,8 +0,0 @@
-[[!comment format=mdwn
- username="joey"
- subject="""comment 1"""
- date="2026-10-29T15:37:53Z"
- content="""
-I have chosen to fix this on the server side. So the server will need to
-upgrade its git-annex-shell, and then this will work.
-"""]]

fix P2P protocol isSpace issues
Fix bug in parsing the P2P protocol that caused breakage when filenames
contained unicode multibyte space characters.
This bug was probably introduced as part of the RawFilePath (and then
OsPath) conversion, since before, the ProtoAssociatedFile instance
would have escaped all unicode isSpace characters and not only ascii spaces
as it does now.
But the real problem is that the P2P protocol has never been intended to
have the words separated by arbitrary unicode space, so splitting on
isSpace is just wrong. Splitting on ' ' fixes the problem.
Note that this means that, when talking to an old version of git-annex,
the multibyte space will be passed to it unchanged, and it then splits
on isSpace, so still fails.
It would be possible to interoperate with old versions, by going back to
escaping all unicode isSpace characters. I don't want to do that because
it moves the P2P protocol in a direction of complexity just to handle a
backwards compatability issue that is rare. I would rather move the P2P
protocol toward a simple correctness.
In situations where it fails, the remote git-annex is an old version,
that is buggy, and so needs to be upgraded.
diff --git a/CHANGELOG b/CHANGELOG
index 99ffd36bfa..fa47ee9724 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,8 @@ git-annex (10.20261027) UNRELEASED; urgency=medium
   * git-annex.cabal: Switch from magic to libmagic.
   * Fix mimetype and mimencoding detection of non-ascii filenames 
     in non-unicode locales.
+  * Fix bug in parsing the P2P protocol that caused breakage when filenames
+    contained unicode multibyte space characters.
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/Utility/SimpleProtocol.hs b/Utility/SimpleProtocol.hs
index 73860216c2..ab921ed790 100644
--- a/Utility/SimpleProtocol.hs
+++ b/Utility/SimpleProtocol.hs
@@ -29,7 +29,6 @@ module Utility.SimpleProtocol (
 	getProtocolLine,
 ) where
 
-import Data.Char
 import GHC.IO.Handle
 import Text.Read
 
@@ -132,7 +131,7 @@ parse5 mk s = mk <$> deserialize p1 <*> deserialize p2 <*> deserialize p3 <*> de
 	(p4, p5) = splitWord rest''
 
 splitWord :: String -> (String, String)
-splitWord = separate isSpace
+splitWord = separate (== ' ')
 
 {- Only safe to use when the serialization does not include whitespace. -}
 parseList :: Serializable p => ([p] -> a) -> Parser a
diff --git a/doc/bugs/SSH_remote_get_fails_with_non-ASCII_whitespace.mdwn b/doc/bugs/SSH_remote_get_fails_with_non-ASCII_whitespace.mdwn
index a53e02f37a..e9880dafd7 100644
--- a/doc/bugs/SSH_remote_get_fails_with_non-ASCII_whitespace.mdwn
+++ b/doc/bugs/SSH_remote_get_fails_with_non-ASCII_whitespace.mdwn
@@ -43,3 +43,4 @@ Claude produced this patch, which I don't understand, but when running "git anne
 
 I've spend the last few days migrating *everything* to git annex, it's wonderful!
 
+> [[fixed|done]] --[[Joey]]
diff --git a/doc/bugs/SSH_remote_get_fails_with_non-ASCII_whitespace/comment_1_4128646029ba561b47a6d872e1eadeb5._comment b/doc/bugs/SSH_remote_get_fails_with_non-ASCII_whitespace/comment_1_4128646029ba561b47a6d872e1eadeb5._comment
new file mode 100644
index 0000000000..c9a22b49a6
--- /dev/null
+++ b/doc/bugs/SSH_remote_get_fails_with_non-ASCII_whitespace/comment_1_4128646029ba561b47a6d872e1eadeb5._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2026-10-29T15:37:53Z"
+ content="""
+I have chosen to fix this on the server side. So the server will need to
+upgrade its git-annex-shell, and then this will work.
+"""]]

be more explict about how the space escaping works
diff --git a/doc/design/p2p_protocol.mdwn b/doc/design/p2p_protocol.mdwn
index d9cf75a02f..544859ad20 100644
--- a/doc/design/p2p_protocol.mdwn
+++ b/doc/design/p2p_protocol.mdwn
@@ -178,9 +178,9 @@ Here AssociatedFile may be the name of a file in the git
 repository, for information purposes only. Or it can be the
 empty string. It will always have unix directory separators.
 
-(Note that in the line-based serialization. AssociatedFile may not contain any
-spaces, since it's not the last token in the line. Use '%' to indicate
-whitespace.)
+(Note that in the line-based serialization. AssociatedFile may not contain
+any spaces, since it's not the last token in the line. Space is escaped
+to '%' and '%' is escaped to '%%'.)
 
 The server may respond with ALREADY-HAVE if it already
 had the content of that key. 

fix tag
diff --git a/doc/bugs/security_hole_data_exfiltration_via_autoenabled_remote.mdwn b/doc/bugs/security_hole_data_exfiltration_via_autoenabled_remote.mdwn
index 9024ef8553..9ffa5fb495 100644
--- a/doc/bugs/security_hole_data_exfiltration_via_autoenabled_remote.mdwn
+++ b/doc/bugs/security_hole_data_exfiltration_via_autoenabled_remote.mdwn
@@ -50,4 +50,4 @@ that any fix to the main attack will also close off this one.)
 
 --[[Joey]]
 
-[fixed|done]]
+[[fixed|done]]

diff --git a/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree.mdwn b/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree.mdwn
new file mode 100644
index 0000000000..866d9d9088
--- /dev/null
+++ b/doc/bugs/info_fails_to_resolve_from_submodule_in_worktree.mdwn
@@ -0,0 +1,64 @@
+### Please describe the problem in your own words.
+
+Currently the command `git annex info` fails   when run inside a submodule that is checked out in a linked Git worktree. The error it provides is:
+
+`changeWorkingDirectory: does not exist `
+
+Ordinary Git commands recognize the worktree correctly, and the same annex command succeeds when `GIT_DIR` and `GIT_WORK_TREE` are explicitly set to absolute paths. 
+
+I think it should work without those overrides.
+
+### What steps will reproduce the problem?
+
+
+This bash script should reproduce the problem on most systems:
+
+```
+#!/bin/sh
+set -eu
+
+workdir=$(mktemp -d)
+cd "$workdir"
+workdir=$(pwd -P)
+
+# Create a repository to use as a submodule.
+git init source
+git -C source commit --allow-empty -m "Initial commit"
+
+# Add it to a parent repository.
+git init main
+git -C main -c protocol.file.allow=always submodule add "$workdir/source" sub
+git -C main commit -m "Add submodule"
+
+# Check out the submodule in a linked worktree.
+git -C main worktree add -b linked ../linked
+git -C linked -c protocol.file.allow=always submodule update --init
+cd linked/sub
+git annex init repro
+
+# Ordinary Git recognizes the worktree.
+git rev-parse --show-toplevel
+git status
+
+# This fails with "changeWorkingDirectory: does not exist".
+# Continue so we can also demonstrate the workaround.
+git annex info || true
+
+# Explicit absolute paths make the same command succeed.
+GIT_DIR=$(git rev-parse --absolute-git-dir) \
+GIT_WORK_TREE=$(pwd -P) \
+git annex info
+```
+
+### What version of git-annex are you using? On what operating system?
+
+I have tried it across a range of versions.
+
+### Please provide any additional information below.
+
+[[!tag projects/repronim]]
+
+### Have you had any luck using git-annex before?
+
+Yes, lots. It's been working well for me in keeping track of scientific analyses.
+

update
diff --git a/doc/todo/remove_dependency_on_crypon_and_tls.mdwn b/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
index 4b2a04097f..d0ae5744f5 100644
--- a/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
+++ b/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
@@ -25,7 +25,11 @@ Here is a plan:
 
 * Switch git-annex to botan for hash functions. There is already a build
   flag, but it would be easier if botan bundled the C library in the
-  haskell package. Which they have considered doing.
+  haskell package. Which they have considered doing.  
+  The `botan-default` branch makes the switch, but is blocked on needing
+  a way to install botan on windows. 
+  [cross compiling with MinGW](https://botan.randombit.net/handbook/building.html#cross-compiling)
+  may be needed, unless a prebuilt release can be found.
 * botan lacks skein and blake2, so would need to get those hashes another
   way
 * Switch from http-client-tls to http-client-openssl.

always build with botan
Note that the windows github workflow for git-annex does not yet have
botan installed, so will fail.
diff --git a/BuildFlags.hs b/BuildFlags.hs
index 7e6bcc8a69..bbd0f36eea 100644
--- a/BuildFlags.hs
+++ b/BuildFlags.hs
@@ -60,16 +60,12 @@ buildFlags = filter (not . null)
 	, "S3"
 	, "WebDAV"
 	, "Servant"
+	, "Botan"
 #ifdef WITH_OSPATH
 	, "OsPath"
 #else 
 #warning Building without the OsPath build flag set results in slower filename manipulation and is not recommended.
 #endif
-#ifdef WITH_BOTAN
-	, "Botan"
-#else 
-#warning Consider building with the Botan build flag set, it speeds up checksumming by up 4x to 8x.
-#endif
 #ifdef WITH_BLAKE3
 	, "Blake3"
 #else
diff --git a/CHANGELOG b/CHANGELOG
index 99ffd36bfa..76ad357dd9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,7 @@ git-annex (10.20261027) UNRELEASED; urgency=medium
   * git-annex.cabal: Switch from magic to libmagic.
   * Fix mimetype and mimencoding detection of non-ascii filenames 
     in non-unicode locales.
+  * Botan is enabled by default; removed build flag.
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/Utility/Hash.hs b/Utility/Hash.hs
index 164c90d993..59ff60ee39 100644
--- a/Utility/Hash.hs
+++ b/Utility/Hash.hs
@@ -11,7 +11,6 @@ module Utility.Hash (module X, props_hashes_stable) where
 
 import Utility.Hash.Types as X
 import Utility.Hash.Incremental as X
-#ifdef WITH_BOTAN
 import Utility.Hash.Botan as X
 -- Fall back to crypton for hashes not in botan.
 import Utility.Hash.Crypton as X
@@ -30,9 +29,6 @@ import Utility.Hash.Crypton as X
 	, blake2bp_512
 	, blake2bp_512_hasher
 	)
-#else
-import Utility.Hash.Crypton as X
-#endif
 #ifdef WITH_BLAKE3
 import Utility.Hash.Blake3 as X
 #endif
diff --git a/Utility/Hash/HMAC.hs b/Utility/Hash/HMAC.hs
index d38084be5e..257032e539 100644
--- a/Utility/Hash/HMAC.hs
+++ b/Utility/Hash/HMAC.hs
@@ -5,9 +5,8 @@
  - License: BSD-2-clause
  -}
 
-{-# LANGUAGE PackageImports #-}
 {-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE OverloadedStrings, CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module Utility.Hash.HMAC (
 	Mac(..),
@@ -19,15 +18,9 @@ import Utility.Hash.Types
 import qualified Data.ByteString as S
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
-#ifdef WITH_BOTAN
 import Botan.Low.MAC
 import Botan.Low.Hash hiding (HashDigest)
 import System.IO.Unsafe (unsafePerformIO)
-#else
-import Utility.Hash.Crypton (hashDigest)
-import "crypton" Crypto.MAC.HMAC hiding (Context)
-import "crypton" Crypto.Hash
-#endif
 
 data Mac = HmacSha1 | HmacSha224 | HmacSha256 | HmacSha384 | HmacSha512
 	deriving (Eq)
@@ -45,19 +38,12 @@ calcMac f mac = case mac of
 	HmacSha384 -> use SHA384
 	HmacSha512 -> use SHA512
   where
-#ifdef WITH_BOTAN
 	use alg k m = unsafePerformIO $ do
 		maccer <- macInit (hmac alg)
 		macSetKey maccer k
 		macUpdate maccer m
 		auth <- macFinal maccer
 		return (f (HashDigest auth))
-#else
-	use alg k m = f (hashDigest (hmacGetDigest (hmacWitnessAlg alg k m)))
-
-	hmacWitnessAlg :: HashAlgorithm a => a -> S.ByteString -> S.ByteString -> HMAC a
-	hmacWitnessAlg _ = hmac
-#endif
 
 -- Check that all the MACs continue to produce the same.
 props_macs_stable :: [(String, Bool)]
diff --git a/debian/control b/debian/control
index 5db7970fb0..a4c1f5b01d 100644
--- a/debian/control
+++ b/debian/control
@@ -90,6 +90,9 @@ Build-Depends:
 	libghc-servant-client-core-dev,
 # not yet in Debian
 #	libghc-libmagic-dev,
+#	libghc-botan-low-dev
+# remove libbotan-3-dev once libghc-botan-low-dev is uncommented
+	libbotan-3-dev,
 	lsof [linux-any],
 	ikiwiki,
 	libimage-magick-perl,
diff --git a/doc/install/fromsource.mdwn b/doc/install/fromsource.mdwn
index 4ebdbf7c1c..d3dfdca144 100644
--- a/doc/install/fromsource.mdwn
+++ b/doc/install/fromsource.mdwn
@@ -41,7 +41,12 @@ and avoids build failures due to fast-changing haskell libraries.
 First, [install stack](https://docs.haskellstack.org/en/stable/install_and_upgrade/)  
 On Debian:
 
-	sudo apt-get install haskell-stack zlib1g-dev
+	sudo apt-get install haskell-stack
+
+The [botan3](https://botan.randombit.net/) 
+library also needs to be installed. On Debian:
+
+	sudo apt-get install libbotan-3-dev
 
 Get the git-annex source code, and inside the source tree run:
 
@@ -81,7 +86,12 @@ Be warned that this involves building a lot of Haskell libraries from
 source, and so it has a lot of moving parts, and it's not uncommon for it
 to be broken from time to time.
 
-Get the git-annex source code, and inside the source tree, run:
+As a prerequisite, the [botan3](https://botan.randombit.net/) 
+library needs to be installed. On Debian:
+
+	sudo apt-get install libbotan-3-dev
+
+Then get the git-annex source code, and inside the source tree, run:
 
 	cabal install -j -f"-assistant -webapp -pairing -dbus -magicmime" --only-dependencies
 	cabal configure -f"-assistant -webapp -pairing -dbus -magicmime"
@@ -101,7 +111,7 @@ into other parts of your HOME directory, run:
 
 To build with all features enabled, including the assistant and webapp,
 you will need to install several C libraries and their headers,
-including libmagic, and zlib. How to do that for your OS is beyond
+including botan3 and libmagic. How to do that for your OS is beyond
 the scope of this page. 
 
 Once the C libraries are installed, run inside the source tree:
diff --git a/git-annex.cabal b/git-annex.cabal
index 39dc10d8ad..b9ca147e20 100644
--- a/git-annex.cabal
+++ b/git-annex.cabal
@@ -35,7 +35,6 @@ Description:
 -- Include only files that are needed make cabal install git-annex work.
 Extra-Source-Files:
   stack.yaml
-  stack-botan.yaml
   stack-NoLLMDependencies.yaml
   cabal.project
   README
@@ -161,10 +160,6 @@ Flag Assistant
 Flag Production
   Description: Enable production build (slower build; faster binary)
 
-Flag Botan
-  Description: Build with the Botan C++ library for faster hashing
-  Default: False
-
 Flag TorrentParser
   Description: Use haskell torrent library to parse torrent files
 
@@ -297,7 +292,8 @@ Executable git-annex
    warp-tls (>= 3.2.2),
    crypton-connection (>= 0.4.3),
    crypton-x509-store,
-   aws (>= 0.24.1)
+   aws (>= 0.24.1),
+   botan-low (< 0.3)
   CC-Options: -Wall
   GHC-Options: -Wall -fno-warn-tabs  -Wincomplete-uni-patterns
   Default-Language: Haskell2010
@@ -353,13 +349,6 @@ Executable git-annex
     Build-Depends:
       filepath-bytestring (>= 1.4.2.1.1)
 
-  if flag(Botan)

(Diff truncated)
update
diff --git a/doc/todo/remove_dependency_on_crypon_and_tls.mdwn b/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
index a2982187b6..4b2a04097f 100644
--- a/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
+++ b/doc/todo/remove_dependency_on_crypon_and_tls.mdwn
@@ -33,5 +33,12 @@ Here is a plan:
   the webapp and p2phttp; the webapp is mostly used on localhost and
   p2phttp is often used behind a server (eg forgejo) that can handle the
   https if it only supported http.
-* Make aws somehow able to use http-client-openssl (eg, by parameterizing it)
-* Make DAV somehow able to use http-client-openssl (eg, by parameterizing it)
+* aws depends on http-client-tls, but only uses it in its test suite;
+  the library uses the Manager that the user passes in.
+  So it would be possible to depend on aws w/o getting http-client-tls as a
+  dependency, except it has a gratuitous dependency on it in the 
+  Library stanza of the cabal file. Easily fixed.
+* Avoid DAV depending on http-client-tls, by passing a Manager to
+  mkDAVContext. (The context can be updated after the fact to use a
+  http-client-tls manager, but it still uses http-client-tls first to make
+  one.)

close
diff --git a/doc/todo/libmagic.mdwn b/doc/todo/libmagic.mdwn
index 8d59dc4fc7..5a61f41274 100644
--- a/doc/todo/libmagic.mdwn
+++ b/doc/todo/libmagic.mdwn
@@ -1,14 +1,3 @@
 The `libmagic` branch switches to a fork of the magic library.
 
-Before merging it, need a test case that filenames travel end to end
-through git-annex to C libmagic correctly. Particularly on Windows
-with filenames that end up containing wide characters.
-There is some possibility of new problems, due to the change to OsPath.
-Seems low, but not tested yet. --[[Joey]]
-
-> Update: Added the test case, waiting to see if it works on windows with
-> magic before switching over. --[[Joey]]
-
-> > Update: The test case fails on windows with magic. Merged libmagic,
-> > will see if that fixes it on windows. In any case, libmagic can't make 
-> > the situation worse. --[[Joey]]
+> [[done]] --[[Joey]]

libmagic 1.3.1 working
Except non-ascii on windows, which has never worked. So disable the
failing test case on windows for now.
Interesting that magicFD didn't work on windows at all. It would be
worth investigating to see how it fails.
diff --git a/CHANGELOG b/CHANGELOG
index cc2616c4bd..99ffd36bfa 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,8 @@ git-annex (10.20261027) UNRELEASED; urgency=medium
     and will be removed after this release. Android users are recommended
     to switch to running `pkg install git-annex` inside termux.
   * git-annex.cabal: Switch from magic to libmagic.
+  * Fix mimetype and mimencoding detection of non-ascii filenames 
+    in non-unicode locales.
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/Test.hs b/Test.hs
index 32d020e832..7d846cd712 100644
--- a/Test.hs
+++ b/Test.hs
@@ -577,8 +577,12 @@ test_magic = intmpclonerepo $ do
 		@? "binary file not added to annex despite mimeencoding config"
 	(not <$> addedannex (literalOsPath "text"))
 		@? "non-binary file got added to annex despite mimeencoding config"
+#ifndef mingw32_HOST_OS
+	-- This is a known failure on windows, see 
+	-- doc/bugs/mimeencoding_detection_is_not_working.mdwn
 	addedannex unicodefn
 		@? "binary file with unicode name not added to annex despite mimeencoding config"
+#endif
   where
 	binarycontent = "\127"
 	-- quasiquoted because fromString for OsString mangles unicode
diff --git a/doc/bugs/mimeencoding_detection_is_not_working/comment_4_3a796f6bbb8d4dce48f5f5b330d8dc1c._comment b/doc/bugs/mimeencoding_detection_is_not_working/comment_4_3a796f6bbb8d4dce48f5f5b330d8dc1c._comment
index db5e0b40f3..7d9b491eae 100644
--- a/doc/bugs/mimeencoding_detection_is_not_working/comment_4_3a796f6bbb8d4dce48f5f5b330d8dc1c._comment
+++ b/doc/bugs/mimeencoding_detection_is_not_working/comment_4_3a796f6bbb8d4dce48f5f5b330d8dc1c._comment
@@ -14,14 +14,5 @@ package, which has some very dodgy LLM generated code around filesystem
 encodings. I have not tested it.)
 
 git-annex has now switched to the haskell libmagic package. That fixes
-the problem on linux with LANG=C. 
-
-The problem still exists on windows though. I think it needs fixing in
-libmagic probably. Also libmagic caused a reversion, even plain ascii
-filenames now don't get handled right on windows. The test suite catches
-that too:
-
-	 magic:                              FAIL (10.51s)
-	      Test.hs:577:
-	      binary file not added to annex despite mimeencoding config
+the problem on linux with LANG=C.
 """]]
diff --git a/doc/bugs/mimeencoding_detection_is_not_working/comment_5_fb0e009ebcde23bdbd8055d94ca49aba._comment b/doc/bugs/mimeencoding_detection_is_not_working/comment_5_fb0e009ebcde23bdbd8055d94ca49aba._comment
new file mode 100644
index 0000000000..c053de907a
--- /dev/null
+++ b/doc/bugs/mimeencoding_detection_is_not_working/comment_5_fb0e009ebcde23bdbd8055d94ca49aba._comment
@@ -0,0 +1,14 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 5"""
+ date="2026-10-28T01:39:46Z"
+ content="""
+The problem still exists on windows though. I think it may not be
+fixable without modifying the C libmagic to use `_wfopen()` or something
+like that. It may not be able to open non-ascii files at all currently.
+
+I tried passing a FD to get around the problem, 
+but that also didn't work right on windows.
+
+For now I've disabled the failing test on windows.
+"""]]

try libmagic 1.3.1 w/o magicFD
Possibly magicFD doesn't work on windows for whatever reason,
but 1.3.1's fixes will make it otherwise work except for the
old problem with unicode filenames.
diff --git a/Annex/Magic.hs b/Annex/Magic.hs
index 4771adada4..b8fb2d02b7 100644
--- a/Annex/Magic.hs
+++ b/Annex/Magic.hs
@@ -1,6 +1,6 @@
 {- Interface to libmagic
  -
- - Copyright 2019-2020 Joey Hess <id@joeyh.name>
+ - Copyright 2019-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -13,8 +13,10 @@ module Annex.Magic (
 	MimeType,
 	MimeEncoding,
 	initMagicMime,
+	getMagicMime,
 	getMagicMimeType,
 	getMagicMimeEncoding,
+	parseMagicMime,
 ) where
 
 import Common
@@ -23,8 +25,9 @@ import Control.Monad.IO.Class
 #ifdef WITH_MAGICMIME
 import Magic
 import Utility.Env
-import Control.Concurrent
-import System.IO.Unsafe (unsafePerformIO)
+import Data.Char
+import qualified Data.List.NonEmpty as NE
+import qualified Data.ByteString as B
 #else
 type Magic = ()
 #endif
@@ -35,7 +38,7 @@ initMagicMime = catchMaybeIO $ do
 	m <- magicOpen [MagicMime]
 	liftIO $ getEnv "GIT_ANNEX_DIR" >>= \case
 		Nothing -> magicLoadDefault m
-		Just d -> magicLoad m $ fromOsPath $
+		Just d -> magicLoad m $ NE.singleton $
 			toOsPath d
 				</> literalOsPath "magic"
 				</> literalOsPath "magic.mgc"
@@ -46,32 +49,20 @@ initMagicMime = return Nothing
 
 getMagicMime :: Magic -> OsPath -> IO (Maybe (MimeType, MimeEncoding))
 #ifdef WITH_MAGICMIME
-getMagicMime m f = Just . parse <$> magicConcurrentSafe (magicFile m (fromOsPath f))
-  where
-	parse s = 
-		let (mimetype, rest) = separate (== ';') s
-		in case rest of
-			(' ':'c':'h':'a':'r':'s':'e':'t':'=':mimeencoding) -> 
-				(mimetype, mimeencoding)
-			_ -> (mimetype, "")
+getMagicMime m f = Just . parseMagicMime <$> magicFile m f
 #else
 getMagicMime _ _ = return Nothing
 #endif
 
+parseMagicMime :: B.ByteString -> (MimeType, MimeEncoding)
+parseMagicMime b = (decodeBS mimetype, decodeBS mimeencoding)
+  where
+	(mimetype, rest) = B.break (== semicolon) b
+	mimeencoding = fromMaybe mempty (B.stripPrefix "; charset=" rest)
+	semicolon = fromIntegral (ord ';')
+
 getMagicMimeType :: MonadIO m => Magic -> OsPath -> m (Maybe MimeType)
 getMagicMimeType m f = liftIO $ fmap fst <$> getMagicMime m f
 
 getMagicMimeEncoding :: MonadIO m => Magic -> OsPath -> m(Maybe MimeEncoding)
 getMagicMimeEncoding m f = liftIO $ fmap snd <$> getMagicMime m f
-
-#ifdef WITH_MAGICMIME
-{-# NOINLINE mutex #-}
-mutex :: MVar ()
-mutex = unsafePerformIO $ newMVar ()
-
--- Work around a bug, the library is not concurrency safe and will
--- sometimes access the wrong memory if multiple ones are called at the
--- same time.
-magicConcurrentSafe :: IO a -> IO a
-magicConcurrentSafe = bracket_ (takeMVar mutex) (putMVar mutex ())
-#endif
diff --git a/CHANGELOG b/CHANGELOG
index 294306b26c..cc2616c4bd 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,7 @@ git-annex (10.20261027) UNRELEASED; urgency=medium
   * The Linux arm64-ancient build has reached end of security support 
     and will be removed after this release. Android users are recommended
     to switch to running `pkg install git-annex` inside termux.
+  * git-annex.cabal: Switch from magic to libmagic.
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/Test.hs b/Test.hs
index e069f6c4a6..32d020e832 100644
--- a/Test.hs
+++ b/Test.hs
@@ -1,11 +1,12 @@
 {- git-annex test suite
  -
- - Copyright 2010-2024 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
 
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
 {-# LANGUAGE CPP #-}
 
 module Test where
@@ -27,6 +28,7 @@ import Control.Concurrent.STM hiding (check)
 import qualified Utility.RawFilePath as R
 import qualified Data.List.NonEmpty as NE
 import Data.String
+import System.OsString (osstr)
 
 import Common
 import CmdLine.GitAnnex.Options
@@ -564,16 +566,24 @@ test_magic = intmpclonerepo $ do
 #ifdef WITH_MAGICMIME
 	git "config" ["annex.largefiles", "mimeencoding=binary"]
 		"git config annex.largefiles"
-	writeFileString (literalOsPath "binary") "\127"
+	writeFileString (literalOsPath "binary") binarycontent
 	writeFileString (literalOsPath "text") "test\n" 
-	git_annex "add" ["binary", "text"]
+	writeFileString unicodefn binarycontent
+	git_annex "add" []
 		"git-annex add with mimeencoding in largefiles"
 	git_annex "sync" ["--no-content"]
 		"git-annex sync"
-	(isJust <$> annexeval (Annex.CatFile.catKeyFile (literalOsPath "binary")))
+	addedannex (literalOsPath "binary")
 		@? "binary file not added to annex despite mimeencoding config"
-	(isNothing <$> annexeval (Annex.CatFile.catKeyFile (literalOsPath "text")))
+	(not <$> addedannex (literalOsPath "text"))
 		@? "non-binary file got added to annex despite mimeencoding config"
+	addedannex unicodefn
+		@? "binary file with unicode name not added to annex despite mimeencoding config"
+  where
+	binarycontent = "\127"
+	-- quasiquoted because fromString for OsString mangles unicode
+	unicodefn = [osstr|fo¡oüঊ|]
+	addedannex f = isJust <$> annexeval (Annex.CatFile.catKeyFile f)
 #else
 	return ()
 #endif
diff --git a/debian/control b/debian/control
index 3c80b61f48..93368f0656 100644
--- a/debian/control
+++ b/debian/control
@@ -77,7 +77,7 @@ Build-Depends:
 	libghc-unbounded-delays-dev,
 	libghc-disk-free-space-dev,
 	libghc-mountpoints-dev,
-	libghc-magic-dev,
+	libghc-libmagic-dev,
 	libghc-socks-dev,
 	libghc-vector-dev,
 	libghc-unliftio-core-dev,
diff --git a/doc/no_llm_code.mdwn b/doc/no_llm_code.mdwn
index 0f1e7361f1..35a415330e 100644
--- a/doc/no_llm_code.mdwn
+++ b/doc/no_llm_code.mdwn
@@ -100,17 +100,6 @@ and 10,000+ lines of changes.
 
 (See [[todo/ditch_yesod]])
 
-### magic
-
-[magic](https://hackage.haskell.org/package/magic) since 1.1.1
-
-[First disclosed LLM generated code](https://github.com/philippedev101/magic-haskell/commit/f52a3628626644d68e1cd1d2c36e98f78693bda5)
-
-The person who took over magic from its long-time maintainer has not
-disclosed their earlier LLM use, but it is apparent, including in their
-communications to [[Joey]] about incorrect LLM-generated statements in the
-changelog.
-
 ### crypton
 
 [crypton](https://hackage.haskell.org/package/crypton) since 1.1.0
@@ -174,3 +163,22 @@ is a way that makes them not be a potential license problem.
 which was merged into gitk upstream of git and so avoided git's policy.
 
 git-annex supports git back to 2.22.
+
+## previous dependencies that contained LLM generated code
+
+[[!toc startlevel=3]]
+
+### magic
+
+[magic](https://hackage.haskell.org/package/magic) since 1.1.1
+

(Diff truncated)
revert libmagic again
This reverts commit c51f910bc4d7d4be682fc7cfefb715b885e2f1a1.
Still failing on windows:
binary file not added to annex despite mimeencoding config
diff --git a/Annex/Magic.hs b/Annex/Magic.hs
index b8fb2d02b7..4771adada4 100644
--- a/Annex/Magic.hs
+++ b/Annex/Magic.hs
@@ -1,6 +1,6 @@
 {- Interface to libmagic
  -
- - Copyright 2019-2026 Joey Hess <id@joeyh.name>
+ - Copyright 2019-2020 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -13,10 +13,8 @@ module Annex.Magic (
 	MimeType,
 	MimeEncoding,
 	initMagicMime,
-	getMagicMime,
 	getMagicMimeType,
 	getMagicMimeEncoding,
-	parseMagicMime,
 ) where
 
 import Common
@@ -25,9 +23,8 @@ import Control.Monad.IO.Class
 #ifdef WITH_MAGICMIME
 import Magic
 import Utility.Env
-import Data.Char
-import qualified Data.List.NonEmpty as NE
-import qualified Data.ByteString as B
+import Control.Concurrent
+import System.IO.Unsafe (unsafePerformIO)
 #else
 type Magic = ()
 #endif
@@ -38,7 +35,7 @@ initMagicMime = catchMaybeIO $ do
 	m <- magicOpen [MagicMime]
 	liftIO $ getEnv "GIT_ANNEX_DIR" >>= \case
 		Nothing -> magicLoadDefault m
-		Just d -> magicLoad m $ NE.singleton $
+		Just d -> magicLoad m $ fromOsPath $
 			toOsPath d
 				</> literalOsPath "magic"
 				</> literalOsPath "magic.mgc"
@@ -49,20 +46,32 @@ initMagicMime = return Nothing
 
 getMagicMime :: Magic -> OsPath -> IO (Maybe (MimeType, MimeEncoding))
 #ifdef WITH_MAGICMIME
-getMagicMime m f = Just . parseMagicMime <$> magicFile m f
+getMagicMime m f = Just . parse <$> magicConcurrentSafe (magicFile m (fromOsPath f))
+  where
+	parse s = 
+		let (mimetype, rest) = separate (== ';') s
+		in case rest of
+			(' ':'c':'h':'a':'r':'s':'e':'t':'=':mimeencoding) -> 
+				(mimetype, mimeencoding)
+			_ -> (mimetype, "")
 #else
 getMagicMime _ _ = return Nothing
 #endif
 
-parseMagicMime :: B.ByteString -> (MimeType, MimeEncoding)
-parseMagicMime b = (decodeBS mimetype, decodeBS mimeencoding)
-  where
-	(mimetype, rest) = B.break (== semicolon) b
-	mimeencoding = fromMaybe mempty (B.stripPrefix "; charset=" rest)
-	semicolon = fromIntegral (ord ';')
-
 getMagicMimeType :: MonadIO m => Magic -> OsPath -> m (Maybe MimeType)
 getMagicMimeType m f = liftIO $ fmap fst <$> getMagicMime m f
 
 getMagicMimeEncoding :: MonadIO m => Magic -> OsPath -> m(Maybe MimeEncoding)
 getMagicMimeEncoding m f = liftIO $ fmap snd <$> getMagicMime m f
+
+#ifdef WITH_MAGICMIME
+{-# NOINLINE mutex #-}
+mutex :: MVar ()
+mutex = unsafePerformIO $ newMVar ()
+
+-- Work around a bug, the library is not concurrency safe and will
+-- sometimes access the wrong memory if multiple ones are called at the
+-- same time.
+magicConcurrentSafe :: IO a -> IO a
+magicConcurrentSafe = bracket_ (takeMVar mutex) (putMVar mutex ())
+#endif
diff --git a/CHANGELOG b/CHANGELOG
index cc2616c4bd..294306b26c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,7 +3,6 @@ git-annex (10.20261027) UNRELEASED; urgency=medium
   * The Linux arm64-ancient build has reached end of security support 
     and will be removed after this release. Android users are recommended
     to switch to running `pkg install git-annex` inside termux.
-  * git-annex.cabal: Switch from magic to libmagic.
 
  -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 18:34:32 -0400
 
diff --git a/Test.hs b/Test.hs
index 32d020e832..e069f6c4a6 100644
--- a/Test.hs
+++ b/Test.hs
@@ -1,12 +1,11 @@
 {- git-annex test suite
  -
- - Copyright 2010-2026 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2024 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
 
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE QuasiQuotes #-}
 {-# LANGUAGE CPP #-}
 
 module Test where
@@ -28,7 +27,6 @@ import Control.Concurrent.STM hiding (check)
 import qualified Utility.RawFilePath as R
 import qualified Data.List.NonEmpty as NE
 import Data.String
-import System.OsString (osstr)
 
 import Common
 import CmdLine.GitAnnex.Options
@@ -566,24 +564,16 @@ test_magic = intmpclonerepo $ do
 #ifdef WITH_MAGICMIME
 	git "config" ["annex.largefiles", "mimeencoding=binary"]
 		"git config annex.largefiles"
-	writeFileString (literalOsPath "binary") binarycontent
+	writeFileString (literalOsPath "binary") "\127"
 	writeFileString (literalOsPath "text") "test\n" 
-	writeFileString unicodefn binarycontent
-	git_annex "add" []
+	git_annex "add" ["binary", "text"]
 		"git-annex add with mimeencoding in largefiles"
 	git_annex "sync" ["--no-content"]
 		"git-annex sync"
-	addedannex (literalOsPath "binary")
+	(isJust <$> annexeval (Annex.CatFile.catKeyFile (literalOsPath "binary")))
 		@? "binary file not added to annex despite mimeencoding config"
-	(not <$> addedannex (literalOsPath "text"))
+	(isNothing <$> annexeval (Annex.CatFile.catKeyFile (literalOsPath "text")))
 		@? "non-binary file got added to annex despite mimeencoding config"
-	addedannex unicodefn
-		@? "binary file with unicode name not added to annex despite mimeencoding config"
-  where
-	binarycontent = "\127"
-	-- quasiquoted because fromString for OsString mangles unicode
-	unicodefn = [osstr|fo¡oüঊ|]
-	addedannex f = isJust <$> annexeval (Annex.CatFile.catKeyFile f)
 #else
 	return ()
 #endif
diff --git a/debian/control b/debian/control
index 93368f0656..3c80b61f48 100644
--- a/debian/control
+++ b/debian/control
@@ -77,7 +77,7 @@ Build-Depends:
 	libghc-unbounded-delays-dev,
 	libghc-disk-free-space-dev,
 	libghc-mountpoints-dev,
-	libghc-libmagic-dev,
+	libghc-magic-dev,
 	libghc-socks-dev,
 	libghc-vector-dev,
 	libghc-unliftio-core-dev,
diff --git a/doc/no_llm_code.mdwn b/doc/no_llm_code.mdwn
index 35a415330e..0f1e7361f1 100644
--- a/doc/no_llm_code.mdwn
+++ b/doc/no_llm_code.mdwn
@@ -100,6 +100,17 @@ and 10,000+ lines of changes.
 
 (See [[todo/ditch_yesod]])
 
+### magic
+
+[magic](https://hackage.haskell.org/package/magic) since 1.1.1
+
+[First disclosed LLM generated code](https://github.com/philippedev101/magic-haskell/commit/f52a3628626644d68e1cd1d2c36e98f78693bda5)
+
+The person who took over magic from its long-time maintainer has not
+disclosed their earlier LLM use, but it is apparent, including in their
+communications to [[Joey]] about incorrect LLM-generated statements in the
+changelog.
+
 ### crypton
 
 [crypton](https://hackage.haskell.org/package/crypton) since 1.1.0
@@ -163,22 +174,3 @@ is a way that makes them not be a potential license problem.
 which was merged into gitk upstream of git and so avoided git's policy.
 
 git-annex supports git back to 2.22.
-
-## previous dependencies that contained LLM generated code
-
-[[!toc startlevel=3]]
-
-### magic
-
-[magic](https://hackage.haskell.org/package/magic) since 1.1.1
-

(Diff truncated)