{
 "number": 35461,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35461",
 "title": "util: Clarify the assertion message in assertion failures (Assert, Assume, etc.)",
 "author": "optout21",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-06-04T11:19:53Z",
 "updated_at": "2026-09-07T17:54:39Z",
 "age_days": 105,
 "draft": false,
 "labels": [
  "Utils/log/libs"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "6494a7e1c863321a5d787ff0e50a51a2e34e9970",
 "head_ref": "2606-assert-msg",
 "head_repo": "optout21/bitcoin",
 "head_history": [
  {
   "t": "2026-06-04T11:24:14Z",
   "sha": "3b5645791fb8f3e769f2d997c83df88c281e4f81"
  },
  {
   "t": "2026-06-04T12:32:29Z",
   "sha": "d46f1fae63ab8d81731ccf0a563119d9edeeeef8"
  },
  {
   "t": "2026-06-17T12:47:15Z",
   "sha": "00b0b0f68a825864481103a16caf2a64559f6951"
  },
  {
   "t": "2026-06-17T12:51:15Z",
   "sha": "f9b679fc3bb41a284395feff0e33a741eb5f7786"
  },
  {
   "t": "2026-06-17T12:51:35Z",
   "sha": "09fbfbdd6c5605baa4dcfc81afd0853dc7c89136"
  },
  {
   "t": "2026-06-17T12:52:02Z",
   "sha": "840da41849c2bdccc127ec8d2dbd1876909cf003"
  },
  {
   "t": "2026-06-17T12:52:53Z",
   "sha": "952c64ca50be450ed6288ca85a5a092a85001b42"
  },
  {
   "t": "2026-09-01T15:37:49Z",
   "sha": "4b63136bea857e8133c0ff3885fa9ec8d83c03ad"
  },
  {
   "t": "2026-09-02T10:13:02Z",
   "sha": "672b6e0ed411a30501f6fcafaf4a88def03d71c6"
  },
  {
   "t": "2026-09-03T11:51:17Z",
   "sha": "6494a7e1c863321a5d787ff0e50a51a2e34e9970"
  }
 ],
 "additions": 52,
 "deletions": 14,
 "changed_files": 4,
 "commit_count": 3,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "l0rinc",
      "url": "https://github.com/bitcoin/bitcoin/pull/35461#issuecomment-5573696803"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "l0rinc": {
   "kind": "ack",
   "hash": "4b63136bea857e8133c0ff3885fa9ec8d83c03ad",
   "t": "2026-09-01T17:43:53Z",
   "stale": true
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 1,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 2,
  "changes_requested": 0,
  "distinct_reviewers": [
   "l0rinc",
   "maflcko"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-07T17:36:09Z",
  "last_reviewer_activity": "2026-09-07T16:58:46Z",
  "last_reviewer": "l0rinc",
  "author_silent_days": 9,
  "waiting_on_author_days": 0,
  "days_since_update": 9
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/test/util_check_tests.cpp",
  "src/util/check.cpp"
 ],
 "body": "Clarify the message in case of assertion failures. Additionally unify the behavior of `NONFATAL_UNREACHABLE` w.r.t. abort handling.\n\nMotivation: This was discussed in https://github.com/bitcoin/bitcoin/pull/34844#discussion_r3303436384\n\nThe error message generated by `Assert`, `Assume`, and `CHECK_NONFATAL` contains the failing assertion condition text, but it's unclear whether that's the failed expectation or the actual case.\n\nFor example,\n`\"Internal bug detected: pindex != nullptr\"`\nin fact means that what happened was `pindex == nullptr`.\n\nClarify the situation, by inserting \"\\`%s\\` check failed\".\n\nThe above example is now:\n``\"Internal bug detected: `pindex != nullptr` check failed\"``\nwhich is more unambiguous. Note: the message for non-assert type errors, such as unreachable from `NONFATAL_UNREACHABLE`, are not affected.\n\nAdditionally, change `NONFATAL_UNREACHABLE` to behave like `CHECK_NONFATAL`, and honor the special abort setting (`G_ABORT_ON_FAILED_ASSUME`).",
 "commits": [
  {
   "sha": "0162db0c8a620e24df74636d10b2633b6e461d27",
   "date": "2026-09-03T10:46:37Z",
   "message": "util: In Assert clarify the failing condition in the message\n\nThe error message generated by Assert, Assume, and CHECK_NONFATAL\ncontained the failing assertion condition text, but it was\nunclear whether that's the failed expectation or the\nactual case. E.g. \"Internal bug detected: pindex != nullptr\"\nmeant that pindex == nullptr.\n\nClarify the situation, by including \"`%s` check failed\".\nThe above example is now:\n\"Internal bug detected: `pindex != nullptr` check failed\"."
  },
  {
   "sha": "0d67983acdfe3ccdb9147994ca05383f5c0d1d41",
   "date": "2026-09-03T10:46:55Z",
   "message": "util: Rename assertion_fail() -> internal_abort_helper()\n\nRename only. Use a more descriptive naming, the helper is for abort."
  },
  {
   "sha": "6494a7e1c863321a5d787ff0e50a51a2e34e9970",
   "date": "2026-09-03T10:47:47Z",
   "message": "util: Unify assertion and unreachable handling\n\nUnreachable error also honors `G_ABORT_ON_FAILED_ASSUME`, and if set,\nit aborts instead of throwing.\nThe condition checking `G_ABORT_ON_FAILED_ASSUME` has been extracted\ninto a new helper, `check_non_fatal_fail()`, and it is reused."
  }
 ],
 "timeline": [
  {
   "t": "2026-06-04T11:24:14Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "3b5645791fb8f3e769f2d997c83df88c281e4f81"
  },
  {
   "t": "2026-06-04T12:32:29Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "d46f1fae63ab8d81731ccf0a563119d9edeeeef8"
  },
  {
   "t": "2026-06-04T12:33:41Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Added a test update (following CI failures), condensed util_check test."
  },
  {
   "t": "2026-06-04T19:36:29Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/util_check_tests.cpp",
   "commit": "d46f1fae63ab8d81731ccf0a563119d9edeeeef8",
   "in_reply_to": null,
   "text": "I don't particularly like the double colon and starting with capital letters in the middle, please consider avoiding the two stacked labels by adding the text after the failure, e.g:\n```patch\ndiff --git a/src/test/util_check_tests.cpp b/src/test/util_check_tests.cpp\nindex 49954dff9c..0d944427f8 100644\n--- a/src/test/util_check_tests.cpp\n+++ b/src/test/util_check_tests.cpp\n@@ -22,16 +22,16 @@ BOOST_AUTO_TEST_CASE(check_fail)\n     test_only_CheckFailuresAreExceptionsNotAborts mock_checks{};\n\n     if constexpr (G_ABORT_ON_FAILED_ASSUME) {\n-        BOOST_CHECK_EXCEPTION(Assume(false), NonFatalCheckError, HasReason{\"Internal bug detected: Failed: 'false'\"});\n+        BOOST_CHECK_EXCEPTION(Assume(false), NonFatalCheckError, HasReason{\"Internal bug detected: `false` check failed\"});\n     } else {\n         BOOST_CHECK_NO_THROW(Assume(false));\n     }\n-    BOOST_CHECK_EXCEPTION(Assert(false), NonFatalCheckError, HasReason{\"Internal bug detected: Failed: 'false'\"});\n-    BOOST_CHECK_EXCEPTION(CHECK_NONFATAL(false), NonFatalCheckError, HasReason{\"Internal bug detected: Failed: 'false'\"});\n+    BOOST_CHECK_EXCEPTION(Assert(false), NonFatalCheckError, HasReason{\"Internal bug detected: `false` check failed\"});\n+    BOOST_CHECK_EXCEPTION(CHECK_NONFATAL(false), NonFatalCheckError, HasReason{\"Internal bug detected: `false` check failed\"});\n\n     // Repeat with a more realistic assert condition\n     void* this_should_be_nonnull{nullptr};\n-    BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{\"Internal bug detected: Failed: 'this_should_be_nonnull != nullptr'\"});\n+    BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{\"Internal bug detected: `this_should_be_nonnull != nullptr` check failed\"});\n }\n\n BOOST_AUTO_TEST_SUITE_END()\ndiff --git a/src/util/check.cpp b/src/util/check.cpp\nindex e26f3d7b41..b62520b1b4 100644\n--- a/src/util/check.cpp\n+++ b/src/util/check.cpp\n@@ -17,7 +17,7 @@\n\n std::string StrFormatFailedCheck(std::string_view assertion)\n {\n-    return strprintf(\"Failed: '%s'\", assertion);\n+    return strprintf(\"`%s` check failed\", assertion);\n }\n\n std::string StrFormatInternalBug(std::string_view msg, const std::source_location& loc)\ndiff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py\nindex 412c9455ce..7fd104a815 100755\n--- a/test/functional/rpc_misc.py\n+++ b/test/functional/rpc_misc.py\n@@ -27,6 +27,7 @@ class RpcMiscTest(BitcoinTestFramework):\n\n         self.log.info(\"test CHECK_NONFATAL\")\n         msg_internal_bug = 'request.params[9].get_str() != \"trigger_internal_bug\"'\n+        msg_failed_check = f\"`{msg_internal_bug}` check failed\"\n         self.restart_node(0)  # Required to flush the chainstate\n         try:\n             node.echo(arg9=\"trigger_internal_bug\")\n@@ -41,7 +42,7 @@ class RpcMiscTest(BitcoinTestFramework):\n             self.start_node(0)\n         except JSONRPCException as e:\n             assert_equal(e.error[\"code\"], -1)\n-            assert f\"Internal bug detected: Failed: '{msg_internal_bug}'\" in e.error[\"message\"]\n+            assert f\"Internal bug detected: {msg_failed_check}\" in e.error[\"message\"]\n\n         self.log.info(\"test max arg size\")\n         ARG_SZ_COMMON = 131071  # Common limit, used previously in the test framework, serves as a regression test\n```"
  },
  {
   "t": "2026-06-04T19:38:19Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/util/check.cpp",
   "commit": "d46f1fae63ab8d81731ccf0a563119d9edeeeef8",
   "in_reply_to": null,
   "text": "Could we avoid adding a public `NonFatalCheckError` factory? The new formatting is only used by Assert, Assume, and CHECK_NONFATAL, so a helper for the formatted string keeps the generic exception constructor as the only public error API:\n```patch\ndiff --git a/src/test/util_check_tests.cpp b/src/test/util_check_tests.cpp\nindex 3a47ecb3c2..49954dff9c 100644\n--- a/src/test/util_check_tests.cpp\n+++ b/src/test/util_check_tests.cpp\n@@ -34,13 +34,4 @@ BOOST_AUTO_TEST_CASE(check_fail)\n     BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{\"Internal bug detected: Failed: 'this_should_be_nonnull != nullptr'\"});\n }\n\n-BOOST_AUTO_TEST_CASE(check_exception_constructor)\n-{\n-    const auto assert_text{\"pointer != nullptr\"};\n-    const auto location{std::source_location::current()};\n-\n-    BOOST_CHECK(std::string_view{NonFatalCheckError(\"some_message\", location).what()}.find(\"Internal bug detected: some_message\\n\") != std::string_view::npos);\n-    BOOST_CHECK(std::string_view{NonFatalCheckError::CreateFailedAssert(assert_text, location).what()}.find(\"Internal bug detected: Failed: 'pointer != nullptr'\\n\") != std::string_view::npos);\n-}\n-\n BOOST_AUTO_TEST_SUITE_END()\ndiff --git a/src/util/check.cpp b/src/util/check.cpp\nindex 36885d6db1..e26f3d7b41 100644\n--- a/src/util/check.cpp\n+++ b/src/util/check.cpp\n@@ -15,6 +15,11 @@\n #include <string>\n #include <string_view>\n\n+std::string StrFormatFailedCheck(std::string_view assertion)\n+{\n+    return strprintf(\"Failed: '%s'\", assertion);\n+}\n+\n std::string StrFormatInternalBug(std::string_view msg, const std::source_location& loc)\n {\n     return strprintf(\"Internal bug detected: %s\\n%s:%d (%s)\\n\"\n@@ -29,17 +34,12 @@ NonFatalCheckError::NonFatalCheckError(std::string_view msg, const std::source_l\n {\n }\n\n-NonFatalCheckError NonFatalCheckError::CreateFailedAssert(std::string_view assertion, const std::source_location& loc)\n-{\n-    return NonFatalCheckError(strprintf(\"Failed: '%s'\", assertion), loc);\n-}\n-\n bool g_detail_test_only_CheckFailuresAreExceptionsNotAborts{false};\n\n void assertion_fail(const std::source_location& loc, std::string_view assertion)\n {\n     if (g_detail_test_only_CheckFailuresAreExceptionsNotAborts) {\n-        throw NonFatalCheckError::CreateFailedAssert(assertion, loc);\n+        throw NonFatalCheckError{StrFormatFailedCheck(assertion), loc};\n     }\n     auto str = strprintf(\"%s:%s %s: Assertion `%s' failed.\\n\", loc.file_name(), loc.line(), loc.function_name(), assertion);\n     fwrite(str.data(), 1, str.size(), stderr);\ndiff --git a/src/util/check.h b/src/util/check.h\nindex 4a7a52b6a8..3ffe8af282 100644\n--- a/src/util/check.h\n+++ b/src/util/check.h\n@@ -57,13 +57,12 @@ struct test_only_CheckFailuresAreExceptionsNotAborts {\n };\n\n std::string StrFormatInternalBug(std::string_view msg, const std::source_location& loc);\n+std::string StrFormatFailedCheck(std::string_view assertion);\n\n class NonFatalCheckError : public std::runtime_error\n {\n public:\n     NonFatalCheckError(std::string_view msg, const std::source_location& loc);\n-    /// Create an instance in case of a failed assert condition, message is constructed accordingly\n-    static NonFatalCheckError CreateFailedAssert(std::string_view assertion, const std::source_location& loc);\n };\n\n /** Internal helper */\n@@ -77,7 +76,7 @@ T&& inline_check_non_fatal(LIFETIMEBOUND T&& val, const std::source_location& lo\n         if constexpr (G_ABORT_ON_FAILED_ASSUME) {\n             assertion_fail(loc, assertion);\n         }\n-        throw NonFatalCheckError::CreateFailedAssert(assertion, loc);\n+        throw NonFatalCheckError{StrFormatFailedCheck(assertion), loc};\n     }\n     return std::forward<T>(val);\n }\n```"
  },
  {
   "t": "2026-06-04T19:39:58Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/util/check.cpp",
   "commit": "d46f1fae63ab8d81731ccf0a563119d9edeeeef8",
   "in_reply_to": null,
   "text": "Should the aborting assertion path use the same clarified wording? Without this, a normal fatal Assert still prints the old Assertion 'condition' failed shape while the exception path says the check failed.\n```patch\ndiff --git a/src/util/check.cpp b/src/util/check.cpp\n--- a/src/util/check.cpp\t(revision e8c1542b132f855eecb2cf621eb280cd35d63cf0)\n+++ b/src/util/check.cpp\t(revision 4ffb91e3bbf0a06166b2a7bbd7f543d2f5c6b36d)\n@@ -41,7 +41,7 @@\n     if (g_detail_test_only_CheckFailuresAreExceptionsNotAborts) {\n         throw NonFatalCheckError{StrFormatFailedCheck(assertion), loc};\n     }\n-    auto str = strprintf(\"%s:%s %s: Assertion `%s' failed.\\n\", loc.file_name(), loc.line(), loc.function_name(), assertion);\n+    auto str = strprintf(\"%s:%s %s: Assertion failed: %s.\\n\", loc.file_name(), loc.line(), loc.function_name(), StrFormatFailedCheck(assertion));\n     fwrite(str.data(), 1, str.size(), stderr);\n     std::abort();\n }\n```"
  },
  {
   "t": "2026-06-04T19:41:54Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/util_check_tests.cpp",
   "commit": "6494a7e1c863321a5d787ff0e50a51a2e34e9970",
   "in_reply_to": null,
   "text": "Could this test the note about non-assert errors directly? Checking NONFATAL_UNREACHABLE keeps the coverage tied to the behavior reviewers need to preserve, instead of only exercising the constructor plumbing.\n```patch\ndiff --git a/src/test/util_check_tests.cpp b/src/test/util_check_tests.cpp\n--- a/src/test/util_check_tests.cpp\t(revision 4ffb91e3bbf0a06166b2a7bbd7f543d2f5c6b36d)\n+++ b/src/test/util_check_tests.cpp\t(revision 014fae1a7202498a6e9022a1f4215b0acf23cc9c)\n@@ -9,6 +9,13 @@\n\n BOOST_AUTO_TEST_SUITE(util_check_tests)\n\n+namespace {\n+void TriggerNonFatalUnreachable()\n+{\n+    NONFATAL_UNREACHABLE();\n+}\n+} // namespace\n+\n BOOST_AUTO_TEST_CASE(check_pass)\n {\n     Assume(true);\n@@ -34,4 +41,9 @@\n     BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{\"Internal bug detected: `this_should_be_nonnull != nullptr` check failed\"});\n }\n\n+BOOST_AUTO_TEST_CASE(unreachable_diagnostic_unchanged)\n+{\n+    BOOST_CHECK_EXCEPTION(TriggerNonFatalUnreachable(), NonFatalCheckError, HasReason{\"Internal bug detected: Unreachable code reached (non-fatal)\"});\n+}\n+\n BOOST_AUTO_TEST_SUITE_END()\n```"
  },
  {
   "t": "2026-06-04T19:45:29Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "d46f1fae63ab8d81731ccf0a563119d9edeeeef8",
   "text": "Concept ACK, it's a tiny difference that we don't often expect to encounter, but when we do, it better tell us a realistic story.\n\nLeft a few nits about slightly clearer phrasing and leaving the new formatter private.\n\nLocal patch\n\n```patch\ndiff --git a/src/test/util_check_tests.cpp b/src/test/util_check_tests.cpp\nindex 3a47ecb3c2..8a92376608 100644\n--- a/src/test/util_check_tests.cpp\n+++ b/src/test/util_check_tests.cpp\n@@ -9,6 +9,13 @@\n\n BOOST_AUTO_TEST_SUITE(util_check_tests)\n\n+namespace {\n+void TriggerNonFatalUnreachable()\n+{\n+    NONFATAL_UNREACHABLE();\n+}\n+} // namespace\n+\n BOOST_AUTO_TEST_CASE(check_pass)\n {\n     Assume(true);\n@@ -22,25 +29,21 @@ BOOST_AUTO_TEST_CASE(check_fail)\n     test_only_CheckFailuresAreExceptionsNotAborts mock_checks{};\n\n     if constexpr (G_ABORT_ON_FAILED_ASSUME) {\n-        BOOST_CHECK_EXCEPTION(Assume(false), NonFatalCheckError, HasReason{\"Internal bug detected: Failed: 'false'\"});\n+        BOOST_CHECK_EXCEPTION(Assume(false), NonFatalCheckError, HasReason{\"Internal bug detected: `false` check failed\"});\n     } else {\n         BOOST_CHECK_NO_THROW(Assume(false));\n     }\n-    BOOST_CHECK_EXCEPTION(Assert(false), NonFatalCheckError, HasReason{\"Internal bug detected: Failed: 'false'\"});\n-    BOOST_CHECK_EXCEPTION(CHECK_NONFATAL(false), NonFatalCheckError, HasReason{\"Internal bug detected: Failed: 'false'\"});\n+    BOOST_CHECK_EXCEPTION(Assert(false), NonFatalCheckError, HasReason{\"Internal bug detected: `false` check failed\"});\n+    BOOST_CHECK_EXCEPTION(CHECK_NONFATAL(false), NonFatalCheckError, HasReason{\"Internal bug detected: `false` check failed\"});\n\n     // Repeat with a more realistic assert condition\n     void* this_should_be_nonnull{nullptr};\n-    BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{\"Internal bug detected: Failed: 'this_should_be_nonnull != nullptr'\"});\n+    BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{\"Internal bug detected: `this_should_be_nonnull != nullptr` check failed\"});\n }\n\n-BOOST_AUTO_TEST_CASE(check_exception_constructor)\n+BOOST_AUTO_TEST_CASE(unreachable_diagnostic_unchanged)\n {\n-    const auto assert_text{\"pointer != nullptr\"};\n-    const auto location{std::source_location::current()};\n-\n-    BOOST_CHECK(std::string_view{NonFatalCheckError(\"some_message\", location).what()}.find(\"Internal bug detected: some_message\\n\") != std::string_view::npos);\n-    BOOST_CHECK(std::string_view{NonFatalCheckError::CreateFailedAssert(assert_text, location).what()}.find(\"Internal bug detected: Failed: 'pointer != nullptr'\\n\") != std::string_view::npos);\n+    BOOST_CHECK_EXCEPTION(TriggerNonFatalUnreachable(), NonFatalCheckError, HasReason{\"Internal bug detected: Unreachable code reached (non-fatal)\"});\n }\n\n BOOST_AUTO_TEST_SUITE_END()\ndiff --git a/src/util/check.cpp b/src/util/check.cpp\nindex 36885d6db1..f67fb5374f 100644\n--- a/src/util/check.cpp\n+++ b/src/util/check.cpp\n@@ -15,6 +15,11 @@\n #include <string>\n #include <string_view>\n\n+std::string StrFormatFailedCheck(std::string_view assertion)\n+{\n+    return strprintf(\"`%s` check failed\", assertion);\n+}\n+\n std::string StrFormatInternalBug(std::string_view msg, const std::source_location& loc)\n {\n     return strprintf(\"Internal bug detected: %s\\n%s:%d (%s)\\n\"\n@@ -29,19 +34,14 @@ NonFatalCheckError::NonFatalCheckError(std::string_view msg, const std::source_l\n {\n }\n\n-NonFatalCheckError NonFatalCheckError::CreateFailedAssert(std::string_view assertion, const std::source_location& loc)\n-{\n-    return NonFatalCheckError(strprintf(\"Failed: '%s'\", assertion), loc);\n-}\n-\n bool g_detail_test_only_CheckFailuresAreExceptionsNotAborts{false};\n\n void assertion_fail(const std::source_location& loc, std::string_view assertion)\n {\n     if (g_detail_test_only_CheckFailuresAreExceptionsNotAborts) {\n-        throw NonFatalCheckError::CreateFailedAssert(assertion, loc);\n+        throw NonFatalCheckError{StrFormatFailedCheck(assertion), loc};\n     }\n-    auto str = strprintf(\"%s:%s %s: Assertion `%s' failed.\\n\", loc.file_name(), loc.line(), loc.function_name(), assertion);\n+    auto str = strprintf(\"%s:%s %s: Assertion failed: %s.\\n\", loc.file_name(), loc.line(), loc.function_name(), StrFormatFailedCheck(assertion));\n     fwrite(str.data(), 1, str.size(), stderr);\n     std::abort();\n }\ndiff --git a/src/util/check.h b/src/util/check.h\nindex 4a7a52b6a8..3ffe8af282 100644\n--- a/src/util/check.h\n+++ b/src/util/check.h\n@@ -57,13 +57,12 @@ struct test_only_CheckFailuresAreExceptionsNotAborts {\n };\n\n std::string StrFormatInternalBug(std::string_view msg, const std::source_location& loc);\n+std::string StrFormatFailedCheck(std::string_view assertion);\n\n class NonFatalCheckError : public std::runtime_error\n {\n public:\n     NonFatalCheckError(std::string_view msg, const std::source_location& loc);\n-    /// Create an instance in case of a failed assert condition, message is constructed accordingly\n-    static NonFatalCheckError CreateFailedAssert(std::string_view assertion, const std::source_location& loc);\n };\n\n /** Internal helper */\n@@ -77,7 +76,7 @@ T&& inline_check_non_fatal(LIFETIMEBOUND T&& val, const std::source_location& lo\n         if constexpr (G_ABORT_ON_FAILED_ASSUME) {\n             assertion_fail(loc, assertion);\n         }\n-        throw NonFatalCheckError::CreateFailedAssert(assertion, loc);\n+        throw NonFatalCheckError{StrFormatFailedCheck(assertion), loc};\n     }\n     return std::forward<T>(val);\n }\ndiff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py\nindex 412c9455ce..7fd104a815 100755\n--- a/test/functional/rpc_misc.py\n+++ b/test/functional/rpc_misc.py\n@@ -27,6 +27,7 @@ class RpcMiscTest(BitcoinTestFramework):\n\n         self.log.info(\"test CHECK_NONFATAL\")\n         msg_internal_bug = 'request.params[9].get_str() != \"trigger_internal_bug\"'\n+        msg_failed_check = f\"`{msg_internal_bug}` check failed\"\n         self.restart_node(0)  # Required to flush the chainstate\n         try:\n             node.echo(arg9=\"trigger_internal_bug\")\n@@ -41,7 +42,7 @@ class RpcMiscTest(BitcoinTestFramework):\n             self.start_node(0)\n         except JSONRPCException as e:\n             assert_equal(e.error[\"code\"], -1)\n-            assert f\"Internal bug detected: Failed: '{msg_internal_bug}'\" in e.error[\"message\"]\n+            assert f\"Internal bug detected: {msg_failed_check}\" in e.error[\"message\"]\n\n         self.log.info(\"test max arg size\")\n         ARG_SZ_COMMON = 131071  # Common limit, used previously in the test framework, serves as a regression test\n```"
  },
  {
   "t": "2026-06-17T12:44:23Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/util/check.cpp",
   "commit": "d46f1fae63ab8d81731ccf0a563119d9edeeeef8",
   "in_reply_to": 3358433169,
   "text": "Good point. Unfortunately, this is not possible/easy to test in a unit test (due to the abort)."
  },
  {
   "t": "2026-06-17T12:46:41Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "d46f1fae63ab8d81731ccf0a563119d9edeeeef8",
   "text": "Thanks for reviewing, @l0rinc; suggestions applied. Summary of changes:\n- Wording: Get rid of double prefix, \"Internal bug detected: 'pindex != nullptr' check failed\" instead of \"Internal bug detected: Failed: 'pindex != nullptr'\"\n- For formatting, use a standalone helper instead of a special factory for the exception\n- In `assertion_fail`, for the non-test-only abort path, also use the same wording.\n- Test: add test for `NONFATAL_UNREACHABLE` (no assert).\n\n(`git diff d46f1fae63ab8d81731ccf0a563119d9edeeeef8 00b0b0f68a825864481103a16caf2a64559f6951`)"
  },
  {
   "t": "2026-06-17T12:47:15Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "00b0b0f68a825864481103a16caf2a64559f6951"
  },
  {
   "t": "2026-06-17T12:51:15Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "f9b679fc3bb41a284395feff0e33a741eb5f7786"
  },
  {
   "t": "2026-06-17T12:51:35Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "09fbfbdd6c5605baa4dcfc81afd0853dc7c89136"
  },
  {
   "t": "2026-06-17T12:52:02Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "840da41849c2bdccc127ec8d2dbd1876909cf003"
  },
  {
   "t": "2026-06-17T12:52:53Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "952c64ca50be450ed6288ca85a5a092a85001b42"
  },
  {
   "t": "2026-08-27T17:51:15Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/util_check_tests.cpp",
   "commit": "952c64ca50be450ed6288ca85a5a092a85001b42",
   "in_reply_to": null,
   "text": "Single quotes around a stringized check collide with character literals in real conditions such as `Assert(cmd.at(0) != '-')`, yielding `... 'cmd.at(0) != '-'' check failed`.\n\nCould we rather use backticks as the outer delimiters so the condition remains visually distinct?"
  },
  {
   "t": "2026-08-27T17:52:30Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/util/check.cpp",
   "commit": "952c64ca50be450ed6288ca85a5a092a85001b42",
   "in_reply_to": null,
   "text": "The fatal path currently renders \"Assertion failed: `<condition>` check failed\", repeating the same outcome around the condition.\n\nCould we render \"Assertion `<condition>` check failed\" once?\n\n```suggestion\n    auto str = strprintf(\"%s:%s %s: Assertion %s.\\n\", loc.file_name(), loc.line(), loc.function_name(), StrFormatFailedCheck(assertion));\n```"
  },
  {
   "t": "2026-08-27T17:53:35Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "952c64ca50be450ed6288ca85a5a092a85001b42",
   "text": "Forgot about this one, please ping me next time if I don't reply in time (thanks @sedited)"
  },
  {
   "t": "2026-09-01T15:36:14Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/util_check_tests.cpp",
   "commit": "952c64ca50be450ed6288ca85a5a092a85001b42",
   "in_reply_to": 3874370498,
   "text": "Good point, applied."
  },
  {
   "t": "2026-09-01T15:36:26Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/util/check.cpp",
   "commit": "952c64ca50be450ed6288ca85a5a092a85001b42",
   "in_reply_to": 3874383404,
   "text": "Applied."
  },
  {
   "t": "2026-09-01T15:37:33Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "952c64ca50be450ed6288ca85a5a092a85001b42",
   "text": "Thanks for the review, @l0rinc; minor message string changes applied.\n\n`git diff 952c64ca50be450ed6288ca85a5a092a85001b42 4b63136bea857e8133c0ff3885fa9ec8d83c03ad`"
  },
  {
   "t": "2026-09-01T15:37:49Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "4b63136bea857e8133c0ff3885fa9ec8d83c03ad"
  },
  {
   "t": "2026-09-01T17:43:53Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "tested ACK 4b63136bea857e8133c0ff3885fa9ec8d83c03ad\n\nThe Alpine failure seems unrelated.\nThe PR description still shows the old `Failed: '<condition>'` wording and should be updated to match the current output."
  },
  {
   "t": "2026-09-01T18:46:48Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/test/util_check_tests.cpp",
   "commit": "672b6e0ed411a30501f6fcafaf4a88def03d71c6",
   "in_reply_to": null,
   "text": "Since you are adding this test, I wonder if the impl should be `#define NONFATAL_UNREACHABLE inline_check_non_fatal(false, current(), \"unreachable\")`, so that `G_ABORT_ON_FAILED_ASSUME` is recognized, just like for `CHECK_NONFATAL`."
  },
  {
   "t": "2026-09-01T18:47:18Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/test/util_check_tests.cpp",
   "commit": "4b63136bea857e8133c0ff3885fa9ec8d83c03ad",
   "in_reply_to": null,
   "text": "nit: Probably no need to repeat this. Could just adjust the above?"
  },
  {
   "t": "2026-09-01T18:49:31Z",
   "kind": "review",
   "who": "maflcko",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "4b63136bea857e8133c0ff3885fa9ec8d83c03ad",
   "text": "lgtm, left two nits"
  },
  {
   "t": "2026-09-02T08:45:40Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nDone. (Thanks for the hint; commit message was updated but not the PR desc.)"
  },
  {
   "t": "2026-09-02T09:55:38Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/util_check_tests.cpp",
   "commit": "4b63136bea857e8133c0ff3885fa9ec8d83c03ad",
   "in_reply_to": 3907287979,
   "text": "Applied."
  },
  {
   "t": "2026-09-02T10:11:29Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/util_check_tests.cpp",
   "commit": "672b6e0ed411a30501f6fcafaf4a88def03d71c6",
   "in_reply_to": 3907283636,
   "text": "This suggestion makes sense, however I'm a bit reluctant to include it in this PR, which is only about message rewording.\nI did the suggestion here:   https://github.com/optout21/bitcoin/commit/5ac866eb21736deb7f6a616aebd066e6d8ec1a80\n\nPlease advise (change it / make new PR with it / include in this PR)."
  },
  {
   "t": "2026-09-02T10:12:58Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "4b63136bea857e8133c0ff3885fa9ec8d83c03ad",
   "text": "Thanks for the comments, @maflcko . One nit applied, the other I've tried, but haven't included currently."
  },
  {
   "t": "2026-09-02T10:13:02Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "672b6e0ed411a30501f6fcafaf4a88def03d71c6"
  },
  {
   "t": "2026-09-02T10:23:29Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/test/util_check_tests.cpp",
   "commit": "672b6e0ed411a30501f6fcafaf4a88def03d71c6",
   "in_reply_to": 3907283636,
   "text": "I really think this can just be a call to `inline_check_non_fatal(false, current(), \"unreachable\")` with the benefits:\n\n* If the `G_ABORT_ON_FAILED_ASSUME` logic ever changes again in the future, it is more likely to apply to all places. (Less code, logic and functions to keep track of)\n* It is less code and the compiler can inline and flatten the `false` literal by itself.\n* All of the macros here are for internal bugs, which should never happen, so the `\"unreachable\"` string is fine and sufficient for a dev-only debug-only string.\n\nPersonally I think it is fine to cycle this into this pull, but no strong opinion."
  },
  {
   "t": "2026-09-02T11:34:38Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/util_check_tests.cpp",
   "commit": "672b6e0ed411a30501f6fcafaf4a88def03d71c6",
   "in_reply_to": 3907283636,
   "text": "A technicality: the `inline_check_non_fatal` was duplicated into a `[[noreturn]]` `check_non_fatal_unreachable` to avoid the compiler giving \"control reaches end of non-void function [-Wreturn-type]\" warnings in a few places where `NONFATAL_UNREACHABLE` is used. While the macro was a simple `throw`, it could be used as a last statement in a non-void method; with the new `if` this is not clear for the compiler, hence the need for a `[[noreturn]]` marker (which cannot be applied on `inline_check_non_fatal`)."
  },
  {
   "t": "2026-09-02T11:53:34Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/test/util_check_tests.cpp",
   "commit": "672b6e0ed411a30501f6fcafaf4a88def03d71c6",
   "in_reply_to": 3907283636,
   "text": "Right. In that case, it could make sense to have a single `[[noreturn]] void check_non_fatal_fai(const std::source_location& loc, std::string_view err_msg);` helper. And then call it in both places:\n\n* In inline_check_non_fatal: `check_non_fatal_fai(loc, assertion);`\n* And in: `#define NONFATAL_UNREACHABLE check_non_fatal_fai( current(), \"unreachable\")`"
  },
  {
   "t": "2026-09-03T11:51:17Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "6494a7e1c863321a5d787ff0e50a51a2e34e9970"
  },
  {
   "t": "2026-09-03T11:57:27Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Reworked based on @maflcko's suggestion, and unified the non-fatal unreachable and check behavior, so that unreachable also honors special abort setting (G_ABORT_ON_FAILED_ASSUME).\nAdded two more commits (but also adjusted the first to be in sync with the latter ones).\n\n- Error cases with an assert include the \"'condition' check failed\" message (but not the unreachable error).\n- Non-fatal unreachable and check cases throw, but abort if G_ABORT_ON_FAILED_ASSUME is set (and thwow if test `g_detail_test_only_CheckFailuresAreExceptionsNotAborts` is set).\n- The condition checking `G_ABORT_ON_FAILED_ASSUME` has been extracted into a new helper `check_non_fatal_fail()`, so it can be reused.\n- The internal helper `assertion_fail()` has been renamed to the more descriptive `internal_abort_helper()`."
  },
  {
   "t": "2026-09-03T17:08:20Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Ready for review. CI failure seems unrelated (MacOS, device out of space)."
  },
  {
   "t": "2026-09-03T17:08:47Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/util_check_tests.cpp",
   "commit": "672b6e0ed411a30501f6fcafaf4a88def03d71c6",
   "in_reply_to": 3907283636,
   "text": "Suggestion applied."
  },
  {
   "t": "2026-09-07T16:57:24Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/util_check_tests.cpp",
   "commit": "952c64ca50be450ed6288ca85a5a092a85001b42",
   "in_reply_to": 3874370498,
   "text": "Please update the PR description after this change"
  },
  {
   "t": "2026-09-07T16:58:46Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "code review reACK 6494a7e1c863321a5d787ff0e50a51a2e34e9970\n\nThe PR description still needs update to use backticks and about `NONFATAL_UNREACHABLE` not being affected"
  },
  {
   "t": "2026-09-07T17:36:09Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nDescription updated, remaining inconsistencies fixed, thanks."
  }
 ],
 "labels_log": [
  {
   "t": "2026-06-04T11:24:41Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-04T13:52:46Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-04T22:02:19Z",
   "action": "labeled",
   "label": "Utils/log/libs",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-17T12:52:32Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-17T14:16:56Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-01T16:49:09Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-02T11:21:36Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-03T13:08:49Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-07T17:54:39Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-06-04T15:38:56Z",
   "kind": "ready_for_review",
   "who": "optout21"
  },
  {
   "t": "2026-06-04T22:02:16Z",
   "kind": "renamed",
   "who": "optout21",
   "from": "Clarify the assertion message in Assert, Assume and CHECK_NONFATAL",
   "to": "util: Clarify the assertion message in Assert, Assume and CHECK_NONFATAL"
  },
  {
   "t": "2026-09-03T11:49:38Z",
   "kind": "convert_to_draft",
   "who": "optout21"
  },
  {
   "t": "2026-09-03T11:56:11Z",
   "kind": "renamed",
   "who": "optout21",
   "from": "util: Clarify the assertion message in Assert, Assume and CHECK_NONFATAL",
   "to": "util: Clarify the assertion message in assertion failures (Assert, Assume, etc.)"
  },
  {
   "t": "2026-09-03T17:07:47Z",
   "kind": "ready_for_review",
   "who": "optout21"
  },
  {
   "t": "2026-09-07T16:48:33Z",
   "kind": "closed",
   "who": "optout21"
  },
  {
   "t": "2026-09-07T16:48:40Z",
   "kind": "reopened",
   "who": "optout21"
  }
 ],
 "text_chars": 23003,
 "text_tokens_estimate": 5750,
 "changed_paths": [
  "src/test/util_check_tests.cpp",
  "src/util/check.cpp",
  "src/util/check.h",
  "test/functional/rpc_misc.py"
 ],
 "files": [
  {
   "path": "src/test/util_check_tests.cpp",
   "add": 19,
   "del": 3
  },
  {
   "path": "src/util/check.cpp",
   "add": 16,
   "del": 3
  },
  {
   "path": "src/util/check.h",
   "add": 15,
   "del": 7
  },
  {
   "path": "test/functional/rpc_misc.py",
   "add": 2,
   "del": 1
  }
 ],
 "test_lines": 25,
 "git": {
  "head": "6494a7e1c863321a5d787ff0e50a51a2e34e9970",
  "head_matches_backup": true,
  "base": "f5e91c6fbae7dc0377dd382814ace063f50e2741",
  "commits": [
   {
    "sha": "0162db0c8a",
    "subject": "util: In Assert clarify the failing condition in the message",
    "files": 4,
    "add": 28,
    "del": 8
   },
   {
    "sha": "0d67983acd",
    "subject": "util: Rename assertion_fail() -> internal_abort_helper()",
    "files": 2,
    "add": 6,
    "del": 6
   },
   {
    "sha": "6494a7e1c8",
    "subject": "util: Unify assertion and unreachable handling",
    "files": 3,
    "add": 24,
    "del": 6
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "e4d0708c4eb038f4",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}