From e4c6393f07903e74aee228962eac622d2c2b14cf Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Tue, 4 Aug 2026 06:30:37 -0700 Subject: [PATCH 1/2] tools: fix ci_changes_per_commit.py crash on null workflowRun check_suite["workflowRun"] is null for check suites that are not attached to a workflow run, for example one that was deleted or one belonging to an app integration. get_commit_depth_and_check_suite() dereferenced it unconditionally: TypeError: 'NoneType' object is not subscriptable which fails the scheduler job's Get-last-commit-with-checks step. Skip those entries rather than indexing into them. --- tools/ci_changes_per_commit.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/ci_changes_per_commit.py b/tools/ci_changes_per_commit.py index 6cbd475fa95..71cfb36cf27 100644 --- a/tools/ci_changes_per_commit.py +++ b/tools/ci_changes_per_commit.py @@ -155,6 +155,11 @@ def get_commit_depth_and_check_suite(query_commits): check_suites = commit["checkSuites"] if check_suites["totalCount"] > 0: for check_suite in check_suites["nodes"]: + # workflowRun is null for check suites not attached to + # a workflow run, such as one that was deleted or one + # belonging to an app integration. + if check_suite["workflowRun"] is None: + continue if check_suite["workflowRun"]["workflow"]["name"] == "Build CI": return [ {"sha": commit_sha, "depth": commit_depth}, From 444e5f951e3bab1bf640cc5a61865fc2b6266ec4 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Sat, 1 Aug 2026 08:38:11 -0700 Subject: [PATCH 2/2] supervisor/web_workflow: check the listener socket calls for failure start_web_workflow() created the listening socket, bound it and called listen() without checking any of them, with an explicit "(Not checking for failures)" comment on the bind. On a port whose network stack can refuse to create a socket, the descriptor stays unset and bind() and listen() then run on -1 and fail with EBADF, but the function still returns true. The caller takes that as success and installs the background callback, so the supervisor polls a socket that was never opened and the board can hang before running code.py. Nothing is ever served, and there is no diagnostic. Observed on a Silicon Labs SiWx917-DK2605A (BRD2605A) running the zephyr-cp port, CircuitPython 10.3.0-alpha.4, where socket() returns ENOTCONN until the interface has an address: socket() ok=0 num=-1 errno=107 ENOTCONN bind port 80 -> 9 errno=9 EBADF listen -> 0 errno=9 closed=1 (returns true) Return false instead of reporting success, and close the socket if bind or listen is the step that failed. Note this is not a retry: the next attempt is the next supervisor_workflow_reset(). Bringing the workflow up once the interface acquires an address later is a separate change. This is not specific to that port. #10054 reports web workflow unreachable after a watchdog reset on several ESP32 boards, and the attempted fix in #10948 moved port 80 from closed to filtered, i.e. bound but never serving, which is the same end state this produces. Co-Authored-By: Claude Opus 5 (1M context) --- supervisor/shared/web_workflow/web_workflow.c | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index f88e3322e23..e0c9ba45095 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -398,14 +398,33 @@ bool supervisor_start_web_workflow(void) { if (common_hal_socketpool_socket_get_closed(&listening)) { #if CIRCUITPY_SOCKETPOOL_IPV6 - socketpool_socket(&pool, SOCKETPOOL_AF_INET6, SOCKETPOOL_SOCK_STREAM, 0, &listening); + bool opened = socketpool_socket(&pool, SOCKETPOOL_AF_INET6, SOCKETPOOL_SOCK_STREAM, 0, &listening); #else - socketpool_socket(&pool, SOCKETPOOL_AF_INET, SOCKETPOOL_SOCK_STREAM, 0, &listening); + bool opened = socketpool_socket(&pool, SOCKETPOOL_AF_INET, SOCKETPOOL_SOCK_STREAM, 0, &listening); #endif + // Ports with an offloaded network stack can refuse to create a + // socket until the interface has an address (this board returns + // ENOTCONN before DHCP completes). Binding and listening on the + // unset descriptor then fails with EBADF, and reporting success + // leaves the supervisor polling a socket that was never opened. + // + // Returning false avoids that but is not a retry: the background + // callback never re-enters this function, so the next attempt is + // the next supervisor_workflow_reset(), i.e. a VM restart. A board + // that only gets an address after boot will not bring the workflow + // up on its own until then. + if (!opened) { + return false; + } common_hal_socketpool_socket_settimeout(&listening, 0); - // Bind to any ip. (Not checking for failures) - common_hal_socketpool_socket_bind(&listening, "", 0, web_api_port); - common_hal_socketpool_socket_listen(&listening, 1); + if (common_hal_socketpool_socket_bind(&listening, "", 0, web_api_port) != 0) { + common_hal_socketpool_socket_close(&listening); + return false; + } + if (!common_hal_socketpool_socket_listen(&listening, 1)) { + common_hal_socketpool_socket_close(&listening); + return false; + } } // Wake polling thread (maybe) socketpool_socket_poll_resume();