{
 "number": 36190,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36190",
 "title": "test: Check bitcoin wrapper child exit status on windows",
 "author": "Bortlesboat",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-09-08T05:16:22Z",
 "updated_at": "2026-09-13T05:57:02Z",
 "age_days": 9,
 "draft": false,
 "labels": [
  "Utils/log/libs"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "f2a1beb29873643ad382deb5d1848bd8572f4105",
 "head_ref": "review/windows-launcher-36105-20260907",
 "head_repo": "Bortlesboat/bitcoin",
 "head_history": [
  {
   "t": "2026-09-09T15:25:19Z",
   "sha": "57b837251ea78c1b884023cc4333991c536c75fc"
  },
  {
   "t": "2026-09-09T16:19:22Z",
   "sha": "fcc3b9c7aef24469e0cd877bc62b5f14fbd31cfc"
  },
  {
   "t": "2026-09-11T04:57:16Z",
   "sha": "b78e0a9d3e004988c54a5438df0fee16d6abc7f3"
  }
 ],
 "additions": 79,
 "deletions": 19,
 "changed_files": 6,
 "commit_count": 6,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "cyb3ralbert",
      "url": "https://github.com/bitcoin/bitcoin/pull/36190#issuecomment-5651512226"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36106,
     "title": "bitcoin wrapper: respect CMAKE_INSTALL_BINDIR/LIBEXECDIR",
     "author": "ryanofsky"
    },
    {
     "number": 36022,
     "title": "test: add coverage for bitcoin wrapper argument handling",
     "author": "cyb3ralbert"
    },
    {
     "number": 32387,
     "title": "ipc: add windows support",
     "author": "ryanofsky"
    }
   ]
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "cyb3ralbert",
   "ryanofsky"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-11T17:21:09Z",
  "last_reviewer_activity": "2026-09-13T05:56:58Z",
  "last_reviewer": "cyb3ralbert",
  "author_silent_days": 5,
  "waiting_on_author_days": 4,
  "days_since_update": 4
 },
 "refs": {
  "mentioned": [
   33229,
   35551,
   36105
  ],
  "depends_on": [
   36105
  ],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 36105,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "bitcoin wrapper: Fix Windows exec so wrapper waits for child process"
   },
   {
    "number": 33229,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-09-25",
    "title": "multiprocess: Don't require bitcoin -m argument when IPC options are used"
   },
   {
    "number": 35551,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-07-29",
    "title": "test: add interface_gui.py to test bitcoin-qt startup"
   }
  ],
  "conflicts": [
   36106,
   36022,
   32387
  ]
 },
 "stack": {
  "shares_commits_with": [
   36105
  ],
  "based_on": [
   36105
  ],
  "base_for": []
 },
 "review_paths": [],
 "body": "Follow-up to #36105, which makes the Windows `bitcoin.exe` wrapper wait for its child and forward the child's exit code. This adds the functional test for that.\n\nThe case worth covering is exit code -1. The first version of the wrapper used `_spawnvp(_P_WAIT)`, which returns -1 both when the spawn fails and when the child started fine and exited with -1, so `bitcoin.exe` reported a startup error and exited 1 instead of passing -1 through. #36105 now uses `_P_NOWAIT` + `_cwait`, which keeps the spawn result and the child status separate. Against the old launcher this test fails at `not(1 == 4294967295)`.\n\nThe test copies `bitcoin.exe` into a temp dir next to a copy of `cmd.exe` named `bitcoind.exe`, so `bitcoin -M node /d /c exit N` runs the fake child, and checks 0, 1 and -1 come back unchanged. Windows reports exit codes as unsigned 32-bit, so -1 arrives as 4294967295. It also checks the wrapper adds nothing to stdout/stderr. The exit code test is Windows only.\n\nryanofsky preferred keeping the runtime fix in #36105 and reviewing the test here (https://github.com/bitcoin/bitcoin/pull/36190#issuecomment-5603692520), so this is the same commit he briefly carried in his branch. The branch still contains #36105's commits and should land after it.\n\nChecked on a native Windows 11 clang/UCRT release build, wallet/GUI/IPC off:\n\n```text\ncmake --build build --target bitcoin bitcoind bitcoin-cli\npython test/functional/tool_bitcoin.py --configfile=build/test/config.ini --tmpdir=build/tool-bitcoin-exit-status\n```\n\nFails against the pre-fix launcher, passes against the fixed one, and the rest of `tool_bitcoin.py` still passes. Use a fresh `--tmpdir` when re-running.",
 "commits": [
  {
   "sha": "6bce70ad0330467abc38e5ee23f3fc3b4379112b",
   "date": "2026-08-26T15:34:02Z",
   "message": "util: Fix ExecVp on Windows to wait for child process\n\nSwitch from _execvp to _spawnvp(_P_NOWAIT) + _cwait so bitcoin.exe\nwaits for the child process to finish and forwards its exit code.\nPreviously _execvp would exit the parent as soon as the child started,\nmaking it impossible for anything waiting on bitcoin.exe (such as a test\nframework) to track whether the child succeeded or failed. _P_NOWAIT is\nused instead of _P_WAIT so that a child exit code of -1 (0xffffffff) is\nnot confused with a spawn failure: _spawnvp(_P_WAIT) returns -1 for both,\nbut _spawnvp(_P_NOWAIT) returns the child handle on success, and _cwait\nfills a separate status that is forwarded via _exit.\n\nCo-Authored-By: Bortlesboat <169967362+Bortlesboat@users.noreply.github.com>\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "68ae4a0f84b12587b9e2b53d3e306460fad4244a",
   "date": "2026-08-26T15:34:02Z",
   "message": "bitcoin: Fix msvcrt EINVAL regression from _execvp to _spawnvp switch\n\nTreat EINVAL from _spawnvp as \"not found\" on msvcrt so the wrapper moves on\nto the next candidate path. msvcrt's _spawnvp returns EINVAL rather than\nENOENT when the file name has a directory component and the file does not\nexist (see code comment), so after the previous commit switched from _execvp\nto _spawnvp, msvcrt builds threw on the first nonexistent candidate,\nlibexec/bitcoind, instead of trying the others. _execvp and ucrt's _spawnvp\nreturn ENOENT, so ucrt builds (MinGW-ucrt and MSVC) are unaffected and the\nchange is guarded with !defined(_UCRT).\n\nMeasured by calling the CRT functions directly on GitHub's windows-2022 and\nwindows-2025 runners, through msvcrt.dll and ucrtbase.dll via ctypes and\nthrough MinGW (msvcrt and ucrt) and MSVC builds:\nhttps://github.com/ryanofsky/bitcoin/actions/runs/34365395636\nhttps://github.com/ryanofsky/bitcoin/actions/runs/34367754505\n\nCo-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
  },
  {
   "sha": "ac15f5654aec8ae96fc814cbab331c580f790250",
   "date": "2026-08-26T15:34:02Z",
   "message": "test: Enable tool_bitcoin.py and interface_gui.py tests on Windows\n\nNow that util::ExecVp on Windows uses _spawnvp(_P_NOWAIT) + _cwait\ninstead of _execvp, the bitcoin wrapper process blocks until the child\nexits and Python can capture its stdout/stderr and exit code normally.\nRemove the Windows skips added in #33229 and #35551.\n\nThe interface_gui.py test is still skipped in vcpkg Qt builds. vcpkg builds Qt\nwith -opengl dynamic, making the minimal platform plugin unusable due to\ninternal Qt bugs. This matches existing logic in src/qt/test/CMakeLists.txt\navoiding the minimal platform plugin with test_bitcoin-qt.\n\nCo-Authored-By: Hodlinator <172445034+hodlinator@users.noreply.github.com>"
  },
  {
   "sha": "2b8779842a3fcb4afaa533a0490b9618ccc42c36",
   "date": "2026-08-26T15:34:02Z",
   "message": "bitcoin: Use generic_category for errno from exec/spawn CRT functions\n\nSwitch from system_category to generic_category when throwing from\nExecVp failure, so errno is read as a POSIX value. On Windows,\nsystem_category interprets codes as Win32 errors, so EINVAL=22 would\nproduce \"The device does not recognize the command\" instead of\n\"Invalid argument\".\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "b78e0a9d3e004988c54a5438df0fee16d6abc7f3",
   "date": "2026-09-11T04:55:39Z",
   "message": "test: Check bitcoin wrapper child exit status on windows\n\nThis detects a bug that was present in the original implementation of the\nearlier commit \"Fix ExecVp on Windows to wait for child process\"\n\nCo-Authored-By: Ryan Ofsky <ryan@ofsky.org>"
  },
  {
   "sha": "f2a1beb29873643ad382deb5d1848bd8572f4105",
   "date": "2026-09-11T17:21:09Z",
   "message": "test: Check bitcoin wrapper reports launch errors\n\nMake sure the wrapper still fails with its own error when bitcoind can't\nbe started, instead of returning something that looks like a child exit\nstatus.\n\nCo-authored-by: cyb3ralbert <cyberalbert@protonmail.ch>"
  }
 ],
 "timeline": [
  {
   "t": "2026-09-09T02:21:05Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "Thanks @Bortlesboat! I added your fix and test to #36105 since I think it makes more sense to change the execvp code once instead of multiple times. I think with those changes it probably makes sense to close this PR. I can also revert or partially revert though if you'd prefer this PR to be separate."
  },
  {
   "t": "2026-09-09T04:51:54Z",
   "kind": "comment",
   "who": "Bortlesboat",
   "assoc": "CONTRIBUTOR",
   "text": "Thanks Ryan. I'd prefer to keep the exit-status regression here as a test-only follow-up, with the runtime fix staying in #36105. Would you be happy to leave the test and its imports out of your branch? I can rebase this PR once that's done."
  },
  {
   "t": "2026-09-09T14:38:51Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "re: https://github.com/bitcoin/bitcoin/pull/36190#issuecomment-5596003309\n\n[quoted text omitted]\nNo problem, dropped the test and happy to review it here"
  },
  {
   "t": "2026-09-09T15:25:19Z",
   "kind": "force_push",
   "who": "Bortlesboat",
   "commit": "57b837251ea78c1b884023cc4333991c536c75fc"
  },
  {
   "t": "2026-09-09T16:19:22Z",
   "kind": "force_push",
   "who": "Bortlesboat",
   "commit": "fcc3b9c7aef24469e0cd877bc62b5f14fbd31cfc"
  },
  {
   "t": "2026-09-11T04:57:16Z",
   "kind": "force_push",
   "who": "Bortlesboat",
   "commit": "b78e0a9d3e004988c54a5438df0fee16d6abc7f3"
  },
  {
   "t": "2026-09-11T13:50:41Z",
   "kind": "comment",
   "who": "cyb3ralbert",
   "assoc": "CONTRIBUTOR",
   "text": "Tested the test commit (`b78e0a9d`) on both Linux and a native Windows runner. It does what it says: reverting `_P_NOWAIT` + `_cwait` back to `_P_WAIT` is caught, and the `-1` case is the one that catches it.\n\nOne gap I'd suggest closing while this is still open.\n\nThe bug the commit message refers to was actually a conflation: `_spawnvp(_P_WAIT)` returns `-1` both when the spawn fails and when the child exits with `-1`. The test covers one side of that \u2014 a child exiting with `-1` is forwarded \u2014 but not the other: a failed spawn must still be reported as a wrapper error rather than forwarded as a child status.\n\nNothing in `tool_bitcoin.py` currently covers the failure path, and the branch carries a commit dedicated to it (68ae4a0f, where MSVCRT returns `EINVAL` instead of `ENOENT` for a path with a directory component).\n\nLeaving `bitcoind` out of the wrapper directory is enough, since the wrapper is invoked by absolute path and therefore does not fall back to a PATH search:\n\n```python\n        # The other half of what _P_NOWAIT + _cwait separates: a child that\n        # could not be launched at all must be reported as a wrapper error, not\n        # forwarded as a child exit status. Leave the internal executable out so\n        # the lookup fails. The wrapper is invoked by absolute path, so it does\n        # not fall back to searching PATH.\n        self.log.info(\"Ensure bitcoin reports a launch failure instead of a child exit status\")\n        missing_dir = self.nodes[0].datadir_path / \"exit_status_missing\"\n        missing_dir.mkdir()\n        missing_wrapper = missing_dir / \"bitcoin.exe\"\n        shutil.copyfile(self.get_binaries().paths.bitcoin_bin, missing_wrapper)\n        result = subprocess.run([str(missing_wrapper), \"-M\", \"node\", \"-version\"],\n                                capture_output=True, timeout=30)\n        assert_equal(result.returncode, 1)\n        assert b\"failed to execute\" in result.stderr\n```\n\nI ran this on a `windows-2022` runner against the cross-built executables from this branch, with both CRTs, and it passes: the wrapper exits with `1` and reports its own error instead of forwarding a child status.\n\nI also rebuilt the branch with `ExecVp` reverted to `_spawnvp(_P_WAIT)` and ran the test against that, with both CRTs: it fails at `AssertionError: not(1 == 4294967295)`, as described. Worth noting that the launch-failure case is never reached there \u2014 the old code did report a failed spawn correctly, so that side genuinely was left uncovered.\n\nOne more thing that fell out of running the above: the message the user gets differs between CRTs.\n\n```\nMSVCRT: Error: execvp failed to execute '...\\bitcoind': Invalid argument\nUCRT:   Error: execvp failed to execute '...\\bitcoind': No such file or directory\n```\n\nThat is the `EINVAL` from 68ae4a0f surfacing: the intermediate lookups treat it as \"not found\" and move on, but the last attempt has `allow_notfound=false`, so `errno` goes into the `std::system_error` as-is, and an MSVCRT build reports \"Invalid argument\" for a file that simply isn't there. Not a problem with this test \u2014 if anything it's an argument for covering the case \u2014 and the fix would belong in #36105.\n\nEnvironment:\n\n* Linux: Debian, GCC 12.2.0, Release, `BUILD_TESTS=OFF`, `ENABLE_IPC=OFF`, Python 3.11 \u2014 `tool_bitcoin.py` passes; the Windows case is skipped.\n* Windows: `windows-2022` runner with the UCRT and MSVCRT cross-built binaries from this branch \u2014 `tool_bitcoin.py` passes on both."
  },
  {
   "t": "2026-09-11T16:58:11Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "re: https://github.com/bitcoin/bitcoin/pull/36190#issuecomment-5635405386\n\nI had trouble understanding the comment at first, but it's just suggesting adding a second test to accompany the existing test in b78e0a9d3e004988c54a5438df0fee16d6abc7f3. The existing test confirms exit codes (0, 1, -1) from `bitcoind` all get passed through the `bitcoin` wrapper. But it doesn't confirm that an `execvp failed to execute` error appears if `bitcoind` can't be started at all.\n\nThis seems like a nice thing to check. The original bug in 80fc82c4aed020afc0f8e2230886733282f9b755 fixed in 6bce70ad0330467abc38e5ee23f3fc3b4379112b was that the `execvp failed to execute error` previously trigger incorrectly when `bitcoind` returned -1, and now it will correctly exit with -1 instead. But it would be good to check that the error still does trigger in cases where it is supposed to trigger."
  },
  {
   "t": "2026-09-13T05:56:58Z",
   "kind": "comment",
   "who": "cyb3ralbert",
   "assoc": "CONTRIBUTOR",
   "text": "re: https://github.com/bitcoin/bitcoin/pull/36190#issuecomment-5637842487\n\nThanks for restating my points more clearly than my original comment. That's exactly what was needed.\n\nACK f2a1beb29873643ad382deb5d1848bd8572f4105\n\nRan `tool_bitcoin.py` against this head on Linux (Debian, GCC 12.2.0) and on a `windows-2022` runner with the cross-built binaries, both CRTs. It passes in all three, and the new case is reached in each.\n\n@Bortlesboat thanks for catching the `copyfile` \u2192 `copy` issue \u2014 my snippet only ever ran under the Windows branch, so it got away without the executable bit. Checking this on every platform is better too."
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-08T05:16:25Z",
   "action": "labeled",
   "label": "Utils/log/libs",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-09T16:20:58Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-09T17:40:21Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-09-09T15:25:23Z",
   "kind": "renamed",
   "who": "Bortlesboat",
   "from": "util: preserve Windows child exit status -1",
   "to": "test: Check Windows wrapper child exit status"
  },
  {
   "t": "2026-09-09T15:25:28Z",
   "kind": "ready_for_review",
   "who": "Bortlesboat"
  },
  {
   "t": "2026-09-09T16:14:32Z",
   "kind": "renamed",
   "who": "Bortlesboat",
   "from": "test: Check Windows wrapper child exit status",
   "to": "test: Check bitcoin wrapper child exit status on windows"
  }
 ],
 "text_chars": 10851,
 "text_tokens_estimate": 2712,
 "changed_paths": [
  "src/bitcoin.cpp",
  "src/util/exec.cpp",
  "test/config.ini.in",
  "test/functional/interface_gui.py",
  "test/functional/test_framework/test_framework.py",
  "test/functional/tool_bitcoin.py"
 ],
 "files": [
  {
   "path": "src/bitcoin.cpp",
   "add": 16,
   "del": 1
  },
  {
   "path": "src/util/exec.cpp",
   "add": 11,
   "del": 1
  },
  {
   "path": "test/config.ini.in",
   "add": 1,
   "del": 0
  },
  {
   "path": "test/functional/interface_gui.py",
   "add": 6,
   "del": 7
  },
  {
   "path": "test/functional/test_framework/test_framework.py",
   "add": 4,
   "del": 0
  },
  {
   "path": "test/functional/tool_bitcoin.py",
   "add": 41,
   "del": 10
  }
 ],
 "test_lines": 69,
 "git": {
  "head": "f2a1beb29873643ad382deb5d1848bd8572f4105",
  "head_matches_backup": true,
  "base": "a24110cef7fa36138620f1876cd0722944618d07",
  "commits": [
   {
    "sha": "6bce70ad03",
    "subject": "util: Fix ExecVp on Windows to wait for child process",
    "files": 1,
    "add": 11,
    "del": 1
   },
   {
    "sha": "68ae4a0f84",
    "subject": "bitcoin: Fix msvcrt EINVAL regression from _execvp to _spawnvp switch",
    "files": 1,
    "add": 12,
    "del": 0
   },
   {
    "sha": "ac15f5654a",
    "subject": "test: Enable tool_bitcoin.py and interface_gui.py tests on Windows",
    "files": 4,
    "add": 11,
    "del": 18
   },
   {
    "sha": "2b8779842a",
    "subject": "bitcoin: Use generic_category for errno from exec/spawn CRT functions",
    "files": 1,
    "add": 4,
    "del": 1
   },
   {
    "sha": "b78e0a9d3e",
    "subject": "test: Check bitcoin wrapper child exit status on windows",
    "files": 1,
    "add": 25,
    "del": 0
   },
   {
    "sha": "f2a1beb298",
    "subject": "test: Check bitcoin wrapper reports launch errors",
    "files": 1,
    "add": 17,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "d0bae0803dd33d28",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}