{
 "number": 34603,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/34603",
 "title": "wallet: Fix detection of symlinks on Windows",
 "author": "achow101",
 "author_association": "MEMBER",
 "created_at": "2026-02-17T00:34:28Z",
 "updated_at": "2026-09-10T21:29:42Z",
 "age_days": 212,
 "draft": false,
 "labels": [
  "Wallet"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
 "head_ref": "fix-listwallet-recursion-detect-symlinks",
 "head_repo": "achow101/bitcoin",
 "head_history": [
  {
   "t": "2026-02-17T03:00:33Z",
   "sha": "677297e8522a81225bc537795f51468e49d85f5a"
  },
  {
   "t": "2026-02-24T23:12:37Z",
   "sha": "fda778d1cc3e3292239f688646d5cea6f4e9ef3a"
  },
  {
   "t": "2026-03-13T23:05:54Z",
   "sha": "9333d26f67094e781a3efb0077c919fc206ca7b4"
  },
  {
   "t": "2026-03-23T22:05:35Z",
   "sha": "0f3fcdfaba35542c856f1c0a60339b7245e404cb"
  },
  {
   "t": "2026-05-19T21:36:12Z",
   "sha": "36a6c83129e6ec285d19750b31c103a433d05d91"
  },
  {
   "t": "2026-05-26T18:14:57Z",
   "sha": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a"
  }
 ],
 "additions": 57,
 "deletions": 40,
 "changed_files": 5,
 "commit_count": 4,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "sedited",
      "url": "https://github.com/bitcoin/bitcoin/pull/34603#pullrequestreview-4444925610"
     }
    ]
   },
   "conflicts": [
    {
     "number": 34909,
     "title": "wallet, refactor: modularise wallet by extracting out legacy wallet migration",
     "author": "rkrux"
    },
    {
     "number": 19461,
     "title": "multiprocess: Add bitcoin-gui -ipcconnect option",
     "author": "ryanofsky"
    },
    {
     "number": 19460,
     "title": "multiprocess: Add bitcoin-wallet -ipcconnect option",
     "author": "ryanofsky"
    },
    {
     "number": 10102,
     "title": "Multiprocess bitcoin",
     "author": "ryanofsky"
    }
   ]
  }
 },
 "acks_parsed": {
  "sedited": {
   "kind": "ack",
   "hash": null,
   "t": "2026-06-07T11:00:01Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 1,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 1,
  "changes_requested": 0,
  "distinct_reviewers": [
   "fanquake",
   "hebasto",
   "luke-jr",
   "maflcko",
   "sedited"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-05-26T18:14:57Z",
  "last_reviewer_activity": "2026-06-07T11:00:01Z",
  "last_reviewer": "sedited",
  "author_silent_days": 113,
  "waiting_on_author_days": 102,
  "days_since_update": 6
 },
 "refs": {
  "mentioned": [
   31507,
   34418
  ],
  "depends_on": [
   34418
  ],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 34418,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-03-13",
    "title": "qa: Make wallet_multiwallet.py Windows crossbuild-compatible"
   },
   {
    "number": 31507,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "build: Use clang-cl to build on Windows natively"
   }
  ],
  "conflicts": [
   34909,
   19461,
   19460,
   10102
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/util/fs_helpers.cpp",
  "src/wallet/db.cpp",
  "src/wallet/wallet.cpp",
  "test/functional/wallet_multiwallet.py"
 ],
 "body": "This PR implements symlink detection for Windows, since GCC [doesn't support symlinks on Windows](https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/src/filesystem/ops-common.h;h=ba377905a2e90f7baf30c900b090f1f732397e08;hb=refs/heads/releases/gcc-13#l124). This is used by the wallet's `ListDatabases` in order to detect symlinks and avoid recursing them. It's also used in wallet path validation since that is also checking whether paths are symlinks, and we should properly detect symlinks on Windows.\n\nUnder the hood, the `IsSymlink` function uses `std::filesystem::is_symlink` for non-Windows systems. On Windows, it checks a file's attributes to determine if it is a [reparse point](https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points). Strictly speaking, this will also catch files that aren't symlinks, such as directories that are Onedrive mounts. I think it's fine to not be recursively searching such directories.\n\nThis is based on #34418 for the multiwallet test and CI changes that it enables. The last commit also removes the skipping of the symlink checks so that we are testing that the symlink handling behavior on Windows matches that on Linux and MacOS.",
 "commits": [
  {
   "sha": "63fd0550fae4e7fddd4110f3ce0ba553d2ac47f8",
   "date": "2026-05-26T17:23:47Z",
   "message": "fs: Add IsSymlink which can check for Windows symlinks\n\nGCC lacks detection of symlinks on Windows. IsSymlink implements this\ndetection on Windows, and utilizes std::filesystem::is_symlink for other\nsystems."
  },
  {
   "sha": "759f58eadc04bb7eef3165a0b2b42a5f7c3e4d5f",
   "date": "2026-05-26T17:24:08Z",
   "message": "wallet: Use IsSymlink to detect symlinks and disable recursing them\n\nWhen searching the wallets directory for wallets, we want to avoid\nrecursing symlinks. Although recursive_directory_iterator already\ndoesn't recurse symlinks, since GCC doesn't implement symlink detection\non Windows, we need to use IsSymlink to detect symlinks on Windows in\norder to avoid recursing them."
  },
  {
   "sha": "43283902b04f29791e06fffc632d32b975bcba8f",
   "date": "2026-05-26T17:24:10Z",
   "message": "wallet: Detect Windows symlinks in GetWalletPath\n\nSince GCC lacks symlink detection, we need to use IsSymlink to detect\nsymlinks on Windows. Additionally, symlinks to files are reported as\nbeing regular files rather symlnks, so we need to explicitly check with\nIsSymlink that a regular file is not actually a symlink."
  },
  {
   "sha": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "date": "2026-05-26T17:24:10Z",
   "message": "test: Test multiwallet symlinks on Windows"
  }
 ],
 "timeline": [
  {
   "t": "2026-02-17T03:00:33Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "677297e8522a81225bc537795f51468e49d85f5a"
  },
  {
   "t": "2026-02-17T09:41:59Z",
   "kind": "review_comment",
   "who": "fanquake",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "9333d26f67094e781a3efb0077c919fc206ca7b4",
   "in_reply_to": null,
   "text": "Do symlinks work fine with LLVM/Clang? cc @hebasto (#31507)."
  },
  {
   "t": "2026-02-18T15:00:31Z",
   "kind": "review_comment",
   "who": "hebasto",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "9333d26f67094e781a3efb0077c919fc206ca7b4",
   "in_reply_to": 2815920793,
   "text": "When building on Windows either with MSVC or clang-cl, `std::filesystem::is_symlink` successfully detects file and folder symbolic links, but fails to detect [junctions](https://learn.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions#junctions).\n\nSince the in-code comment mentions \"Windows cross compile\", were you actually asking about the [LLVM MinGW](https://github.com/mstorsjo/llvm-mingw) toolchain?"
  },
  {
   "t": "2026-02-18T15:19:24Z",
   "kind": "review_comment",
   "who": "hebasto",
   "assoc": "MEMBER",
   "path": "src/util/fs_helpers.cpp",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "in_reply_to": null,
   "text": "f507a31eec5219c3621b9d5551f9d04c2a409ded\n\nShould we additionally check against a [Reparse Point Tag](https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags) value?"
  },
  {
   "t": "2026-02-18T17:37:19Z",
   "kind": "review_comment",
   "who": "luke-jr",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/wallet_multiwallet.py",
   "commit": "fda778d1cc3e3292239f688646d5cea6f4e9ef3a",
   "in_reply_to": null,
   "text": "Why is this removed? It's actually kind of annoying to have files that need to be chmod'd before deletion"
  },
  {
   "t": "2026-02-18T18:14:40Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "test/functional/wallet_multiwallet.py",
   "commit": "fda778d1cc3e3292239f688646d5cea6f4e9ef3a",
   "in_reply_to": 2823601074,
   "text": "It isn't, the chmod testing is significantly changed. This is from #34418 and review comments about it should be left there."
  },
  {
   "t": "2026-02-18T18:16:09Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/util/fs_helpers.cpp",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "in_reply_to": 2822866906,
   "text": "I considered that, but didn't think any of the possible tag values were things that we actually want to support recursively scanning inside of."
  },
  {
   "t": "2026-02-18T18:50:00Z",
   "kind": "review_comment",
   "who": "luke-jr",
   "assoc": "CONTRIBUTOR",
   "path": "src/util/fs_helpers.cpp",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "in_reply_to": null,
   "text": "Might it be better to just override fs::is_symlink on Windows?"
  },
  {
   "t": "2026-02-18T18:54:21Z",
   "kind": "review_comment",
   "who": "hebasto",
   "assoc": "MEMBER",
   "path": "src/util/fs_helpers.cpp",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "in_reply_to": 2822866906,
   "text": "Since the `IsSymlink` fucntion returns `true` for entities other than symlinks, we should probably add a clarifying comment."
  },
  {
   "t": "2026-02-18T19:14:11Z",
   "kind": "review_comment",
   "who": "luke-jr",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/db.cpp",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "in_reply_to": null,
   "text": "Is it intentional that we still check the symlink itself for a wallet?\n\nIf so, should we not do so even if IsSymlink throws?"
  },
  {
   "t": "2026-02-18T19:18:45Z",
   "kind": "review_comment",
   "who": "luke-jr",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "677297e8522a81225bc537795f51468e49d85f5a",
   "in_reply_to": null,
   "text": "This calls `IsSymlink` twice in some cases."
  },
  {
   "t": "2026-02-18T19:18:48Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/util/fs_helpers.cpp",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "in_reply_to": 2823906061,
   "text": "I considered that, but we aren't already using `fs::is_symlink` so I don't think it would give us any benefit. Also, since `IsSymlink` returns `true` for more than just symlinks, I thought it wouldn't be appropriate to be override the stdlib function."
  },
  {
   "t": "2026-02-18T19:19:16Z",
   "kind": "review_comment",
   "who": "luke-jr",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "in_reply_to": null,
   "text": "Does this need to change? We already check for path_type == fs::file_type::directory"
  },
  {
   "t": "2026-02-18T19:21:54Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/db.cpp",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "in_reply_to": 2824007809,
   "text": "[quoted text omitted]\n\nYes\n\n[quoted text omitted]\n`status()` and `symlink_status()` used below can also throw, so this is the same behavior."
  },
  {
   "t": "2026-02-24T23:06:32Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/util/fs_helpers.cpp",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "in_reply_to": 2822866906,
   "text": "The docstring states that it returns true for reparse points on windows."
  },
  {
   "t": "2026-02-24T23:12:37Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "fda778d1cc3e3292239f688646d5cea6f4e9ef3a"
  },
  {
   "t": "2026-02-24T23:12:56Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "in_reply_to": 2824027663,
   "text": "Hmm, good point. Removed"
  },
  {
   "t": "2026-02-24T23:13:12Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "677297e8522a81225bc537795f51468e49d85f5a",
   "in_reply_to": 2824025119,
   "text": "No longer relevant with https://github.com/bitcoin/bitcoin/pull/34603#discussion_r2824027663"
  },
  {
   "t": "2026-03-13T23:05:54Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "9333d26f67094e781a3efb0077c919fc206ca7b4"
  },
  {
   "t": "2026-03-16T09:14:12Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "9333d26f67094e781a3efb0077c919fc206ca7b4",
   "in_reply_to": null,
   "text": "llm nits:\n\nwindows -> Windows [Proper noun; \"Windows\" should be capitalized in documentation comment] (src/util/fs_helpers.h)\nexplicilty -> explicitly [Spelling error in comment] (this line)"
  },
  {
   "t": "2026-03-23T22:05:35Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "0f3fcdfaba35542c856f1c0a60339b7245e404cb"
  },
  {
   "t": "2026-03-23T22:05:39Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "9333d26f67094e781a3efb0077c919fc206ca7b4",
   "in_reply_to": 2939057933,
   "text": "Fixed"
  },
  {
   "t": "2026-03-23T22:05:52Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "Forgot to undraft this."
  },
  {
   "t": "2026-05-19T21:36:12Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "36a6c83129e6ec285d19750b31c103a433d05d91"
  },
  {
   "t": "2026-05-26T18:14:57Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a"
  },
  {
   "t": "2026-06-07T11:00:01Z",
   "kind": "review",
   "who": "sedited",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
   "text": "utACK\n\nThe changes seem fine to me, but I would suggest adding a note in the description about hebasto's comment https://github.com/bitcoin/bitcoin/pull/34603#discussion_r2822782155 about why we're gating WIN32 in general."
  }
 ],
 "labels_log": [
  {
   "t": "2026-02-17T00:34:32Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-17T01:43:26Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-19T02:15:21Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-25T00:19:17Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-14T00:22:44Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-02T11:07:35Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-19T23:10:47Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-22T04:12:08Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-26T19:45:06Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-03-23T22:05:46Z",
   "kind": "ready_for_review",
   "who": "achow101"
  }
 ],
 "text_chars": 4704,
 "text_tokens_estimate": 1176,
 "changed_paths": [
  "src/util/fs_helpers.cpp",
  "src/util/fs_helpers.h",
  "src/wallet/db.cpp",
  "src/wallet/wallet.cpp",
  "test/functional/wallet_multiwallet.py"
 ],
 "files": [
  {
   "path": "src/util/fs_helpers.cpp",
   "add": 14,
   "del": 0
  },
  {
   "path": "src/util/fs_helpers.h",
   "add": 7,
   "del": 0
  },
  {
   "path": "src/wallet/db.cpp",
   "add": 6,
   "del": 0
  },
  {
   "path": "src/wallet/wallet.cpp",
   "add": 3,
   "del": 1
  },
  {
   "path": "test/functional/wallet_multiwallet.py",
   "add": 27,
   "del": 39
  }
 ],
 "test_lines": 66,
 "git": {
  "head": "b127cde3f4ae99d186d3de62f84bb3c9b7c48b0a",
  "head_matches_backup": true,
  "base": "9c150222604478431669b66b2caa0d75e8324d4c",
  "commits": [
   {
    "sha": "63fd0550fa",
    "subject": "fs: Add IsSymlink which can check for Windows symlinks",
    "files": 2,
    "add": 21,
    "del": 0
   },
   {
    "sha": "759f58eadc",
    "subject": "wallet: Use IsSymlink to detect symlinks and disable recursing them",
    "files": 1,
    "add": 6,
    "del": 0
   },
   {
    "sha": "43283902b0",
    "subject": "wallet: Detect Windows symlinks in GetWalletPath",
    "files": 1,
    "add": 3,
    "del": 1
   },
   {
    "sha": "b127cde3f4",
    "subject": "test: Test multiwallet symlinks on Windows",
    "files": 1,
    "add": 27,
    "del": 39
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "123a484055320da7",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}