Flashing a board from a BenchPod sounds like it should be the easy part: SWD is two wires. The reason it isn't is where the debug host has to live. A GitHub-hosted runner has no board plugged into it, and a pod sitting on someone's desk has no debugger plugged into it either. Something has to bridge the gap, and it has to survive going through a cloud service instead of a USB cable.

Two ways to move SWD bits, and why neither is enough alone
The first version shipped as remote_bitbang: OpenOCD encodes every SWD clock edge as a byte and the pod forwards those bytes to the iCE40's swd_engine, one bit at a time. It works, and it's the fallback that needs nothing but a serial or TCP byte-pipe on the other end. It's also slow by construction: every clock edge is a round trip, so a flash that's instant over a real debug probe takes tens of seconds over the network, and a full 16 KiB image over a chatty link can run into a minute or more.
The fix isn't a faster bitbang loop, it's not bitbanging at all. The pod runs an actual CMSIS-DAP processor in firmware (dap.c), talking to the same swd_engine gateware underneath, but now OpenOCD's cmsis-dap TCP backend ships whole DAP transfers instead of individual clock edges. Same wire in the end, far fewer round trips. The catch: that TCP backend only exists in OpenOCD builds newer than 0.12.0, so anywhere you flash from, including a GitHub runner, needs a recent build, not the distro package.
The path a DAP packet actually takes
There's no direct socket from a laptop or a CI runner to the pod: the pod is behind NAT on someone's bench, so it has to be the one reaching out. It holds an outgoing WebSocket connection to the cloud service, authenticated with a private key baked into the firmware at provision time. Everything downstream rides that one connection:
pytest, running wherever the test runs, opens a request to embeddedci.com for a named device.- The server matches the request to the pod's existing WebSocket session and starts relaying.
- Each CMSIS-DAP transfer gets wrapped as a small JSON message over that WebSocket and handed to the pod's
dap_starthandler. - The pod's DAP processor executes the transfer against the iCE40 and the result flows back the same way.
To OpenOCD on the other end, this looks like a normal cmsis-dap TCP adapter: the tunneling is invisible above the transport layer, which is what lets the exact same pytest test run against a pod on your desk or a pod three time zones away with only the connection string changing.
Proving whose runner gets to drive the pod
The other half of "over the cloud" is authorization, and it's deliberately not an API key sitting in a GitHub secret. A workflow with permissions: id-token: write can mint a short-lived GitHub OIDC token that says, verifiably, this run belongs to repository X. The server checks that token against a per-repo allow-list of device names, so there's no shared secret to leak, rotate, or accidentally commit. This is the same trust model PyPI Trusted Publishing uses, applied to hardware instead of package uploads.
The upshot for a workflow file is that flashing a real board looks almost identical to flashing one over USB: just a different connection string and one permissions block instead of a secret. The full runnable example, including the OpenOCD version pin CI needs, is in Running Hardware CI with GitHub Actions and the GitHub Actions HIL docs.