)]}'
{
  "log": [
    {
      "commit": "64d952c027df194ea8b107b3d5d0b6c4cb9a994b",
      "tree": "1407f23cfc4b667a8bee2f95068440aa5f41d881",
      "parents": [
        "eca6caf673452c8ec940e2acf5e46d0631fb72bf",
        "20bb94fcf9ad7fca7fb685e53307f4d03b1340fd"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 16:09:22 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 28 16:09:22 2026 +0700"
      },
      "message": "Merge pull request #3860 from hathach/claude/hil-blindness\n\nhil: drop the sysfs blindness subsystem and derive the recovery reserve"
    },
    {
      "commit": "20bb94fcf9ad7fca7fb685e53307f4d03b1340fd",
      "tree": "1407f23cfc4b667a8bee2f95068440aa5f41d881",
      "parents": [
        "b190840e1d85f806575f58635827502d714febcf"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 15:26:43 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 14:18:41 2026 +0700"
      },
      "message": "test/hil, docs: move the containment history into the design doc\n\nThe modules were 21% comment, much of it review-cycle argument rather than\nguidance -- _kill_kids stated \u0027descendant by construction, no argv check needed\u0027\ntwice, eight lines apart. Deleting such comments outright makes maintenance\nworse: the next reader simplifies the thing the comment was defending. So the\nhistory moves to the 2026-07-30 fleet-wedge design doc, which gains a trim\naddendum recording what was removed, what was deliberately kept, and the rule\nthat decided each -- the CI ceiling bounds how long a run burns, and does nothing\nabout state that outlives it.\n\nOne comment was not merely long but WRONG: the report wipe carried \u0027The unlink is\nDEFERRED to inside the pool try/except below\u0027, which is the opposite of what the\ncode does -- it sits before Manager() with its own comment explaining why. That\nis the failure mode this pass is about, so it is deleted rather than reworded.\n\nKept everywhere: citations that refute a plausible wrong reading. That\nusb_lock_device_interruptible is why the readers are killable, that usblp_mutex\nis driver-global, that rawmidi honours O_NONBLOCK where usblp does not.\n\nTwo follow-ups are retired with them: pr3803-hil-blindness-reporting.md (there\nis no blindness to report any more) and pr3803-usbtest-recovery-reserve.md (the\nreserve is derived now). Kept: pr3803-flasher-recover.md, which PR #3832\nimplements, plus pr3803-pci-rebind-stranding.md and pr3803-hil-iar-rerun-spec.md,\nboth independent of this work.\n"
    },
    {
      "commit": "b190840e1d85f806575f58635827502d714febcf",
      "tree": "76c6abf45f3670313d5812f3c453d849a98371f9",
      "parents": [
        "a5938f71fbc704c85979121bb9e37d5d636ef20d"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 15:26:43 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 14:18:41 2026 +0700"
      },
      "message": "test/hil: drop the sysfs blindness subsystem and derive the recovery reserve\n\nTwo layers whose cost was a contract to reason about rather than an outcome.\n\nSYSFS_UNKNOWN was a three-valued return five files had to keep apart, and\nmisreading unknown as absence was silent: a healthy board reported as a firmware\nregression. What it guarded is real -- `serial` is served by usb_string_attr,\nwhich takes usb_lock_device_interruptible (v6.12.96 sysfs.c:141-143), the same\nlock a wedged usbfs ioctl holds -- so the BOUND stays, on every caller by\ndefault. usb_scan reads `serial` on every device matching the VID, and hil_lock\u0027s\ncontroller_of does that on essentially every board, so one wedged DUT would\notherwise stall every worker, not one. What goes is the third value.\n\nread_sysfs now returns str or None, and the question the third value existed to\nanswer is asked directly instead, by two predicates that say which question they\nanswer: sysfs_stranded() is process-wide and sticky, for hil_pool_check\u0027s footer\n(\"could anything here be the tool losing sight of healthy hardware?\"), and\npath_stranded(path) is per-device, which is what usbtest needs to tell a DUT\nwhose `serial` is held under device_lock from one that genuinely left the bus --\nthat difference decides whether it performs driver-registry writes that take the\nuninterruptible device_lock.\n\nGone: _SysfsUnknown, SYSFS_UNKNOWN, sysfs_blind, sysfs_blind_note,\nnote_sysfs_strand, the cross-process blindness publishing and its report banner,\nusb_scan\u0027s (list, bool) return, usbtest\u0027s inconclusive abort, _blind_note\u0027s slot\nin the result tuple, and bounded_open, whose last caller went in the previous\ncommit.\n\nThe strand memo is rewritten around the one invariant that makes it safe to\nreuse: it is keyed by the path\u0027s kernfs inode, captured BEFORE the read. A\nbusport does not change when a board returns to the same physical port, so a\npath-only blacklist outlives the wedge and hil_pool_check\u0027s own recovery flow --\nreset, reflash, wait_device polling that busport -- would never look at the\nboard again. A re-enumeration destroys the kernfs node and makes a new one, so a\nchanged inode is the all-clear. Two ceilings bound different things: per path\n(_PATH_STRAND_MAX) for a board that flaps while still wedged, and per process\n(_STRAND_MAX) as a backstop against RLIMIT_NOFILE, counted per PATH rather than\nper reader because hil_pool_check runs four poll threads over one bus.\n\nA board the pool guard never reached is now reported as run-aborted rather than\npool-timed-out, and outranks a stale board-locked cell for the same reason the\npool-timeout cell does.\n\nBoth predicates answer conservatively where they are consulted before something\nirreversible. path_stranded() covers the paths read_sysfs answered None for\nWITHOUT reading -- past _STRAND_MAX it declines to start another reader, and\nvouching for a path nobody looked at hands usbtest\u0027s fail-CLOSED guard a\nfabricated all-clear, running remove_id/unbind against a wedged device. usbtest\u0027s\nstartup lookup carries the same caveat hil_test\u0027s absent arm already did, because\nits stderr is relayed verbatim into the report cell.\n\nstrand_note() survives the removal for the same reason master had it: every\ncaller that can say \"not found\" needs the same sentence, and the one site left to\nre-invent it got missed -- a wedged-but-enumerated printer was reported as an\nenumeration failure, sending a maintainer after firmware.\n\nThe two predicates are not interchangeable, and usbtest needs both. Its per-case\nverdict is per-DUT -- a peer that stranded at case 2 must not make our board\nreport wedged at case 29 -- but the finally block\u0027s cleanup is process-wide:\nremove_id plus an unbind of EVERY interface under the driver, including that\npeer\u0027s, each taking the uninterruptible device_lock. So the verdict uses\npath_stranded() and the global cleanup stays gated on sysfs_stranded().\n\nUSBTEST_RECOVERY_BUDGET was an independent 250s that could not actually contain\nthe ladder it reserved for, which is why usbtest.py carried a _time_left() gate\nre-deciding before every step -- with a bare \u0027- 35\u0027 for costs paid downstream\nthat nobody could re-derive. Between them the two produced a recovery that\nskipped its own steps for most real hangs.\n\nThe reserve is now derived from the bounds usbtest itself declares, per flasher\nand per target: a probe reset, a reflash, and the Rescue-DP POR plus retry a\nwedged RP DAP needs, plus the settles and hil_util.REAP_GRACE for each bounded\nstep. The Rescue-DP legs are openocd-only and gated on the RP target cfg, and\na stub reset is screened out, so the reserve tracks each board\u0027s real ladder\ninstead of one fleet number: 390s for the two RP boards -- whose ladder the old\n250 could not contain, which is exactly why the gates skipped their steps --\n190s for the other seventeen probe-reset boards, and 150s for esptool and\nlm4flash, whose reset is a no-op. Changing a bound in usbtest moves the\nreserve with it, and a unit test asserts it covers the ladder.\n\nWith the room actually reserved, the child runs the ladder straight through:\nrecovery_steps, _time_left, the three per-step gates and the parsed-but-unused\n--outer-timeout are gone. What stays is what decides outcomes -- the convoy_safe\ngate, reset-before-reflash, the no_op screen so a stub that resets nothing is not\nclaimed, and wedged_pids() as the arbiter, because a clean flash only proves the\nprobe wrote the MCU.\n\nhil_util.py 616 -\u003e 514 lines.\n"
    },
    {
      "commit": "a5938f71fbc704c85979121bb9e37d5d636ef20d",
      "tree": "b621024858993dec18d3d183c35b59c99ca28ec3",
      "parents": [
        "eca6caf673452c8ec940e2acf5e46d0631fb72bf"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 15:26:43 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 14:18:41 2026 +0700"
      },
      "message": "test/hil: run the printer write in a child, like the read\n\ntest_device_printer_to_cdc opened /dev/usb/lp* on the worker itself and let\nhil_util.bounded_open abandon a thread when the open blocked. usblp allows one\nopener -- usblp_open() returns -EBUSY while usblp-\u003eused (v6.12.96 usblp.c) --\nso the abandoned thread\u0027s fd poisoned the node for every later test that worker\nran. The read half already avoided this by forking; the write half now does too,\nvia the same run_alongside, and a killed child takes its fd with it.\n\nThis removes the only production caller of bounded_open.\n"
    },
    {
      "commit": "eca6caf673452c8ec940e2acf5e46d0631fb72bf",
      "tree": "ce197bf019b29712095c3768c0fc23b26499cc79",
      "parents": [
        "4c7bb3ced98cb7262dd8eefd6c9f7df6e42ffcee"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 14:16:02 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 28 14:16:02 2026 +0700"
      },
      "message": "Add RTT console/capture tooling (tools/rtt.py), rtt skill, and HIL harness support (#3853)\n\nPromote SEGGER RTT from an inline debugging technique to a standalone skill\nbacked by one stdlib-only implementation in tools/rtt.py: a CLI and importable\nmodule for console/capture over J-Link (RTTTelnetPort) and OpenOCD (rtt server)\nprobes, with probe selection by serial or VID:PID, control-block address via\n--elf or --addr, bidirectional console, post-mortem ring dump, and\n--reset-before-attach for boot-time capture. The HIL harness reads a board\u0027s\nconsole over RTT when its probe has no VCOM (\"logger\": \"rtt\" plus a LOGGER\u003drtt\nvariant define), covering device_info, pool-check aliveness, and CI wiring.\nValidated on 22 boards across both backends; 26 unit tests run in pre-commit."
    },
    {
      "commit": "4c7bb3ced98cb7262dd8eefd6c9f7df6e42ffcee",
      "tree": "73a5bc84e66a78fc8e7a7fb0c36d4f7644063768",
      "parents": [
        "d2b4d8994eb2a1b8994601069f5180a9c979ee8c"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 14:10:33 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 28 14:10:33 2026 +0700"
      },
      "message": "Merge pull request #3863 from hathach/claude/validate-loop\n\nvalidate workflow: loop validate -\u003e fix cycles until green\n\nTurn the single-pass validate gate into a loop: run unit + builds +\nsize + PVS + claude/codex reviews in parallel; on a red verdict one\nfix agent repairs the gate-failing evidence (CONFIRMED findings,\ncodex P0/P1, failed stages - PLAUSIBLE/quality stay report-only),\ncommits, and the affected stages re-run, up to maxCycles (default 5).\n\nHardened per review: fix-commit paths verified from git rather than\nself-report, restartRequired when a fix edits the workflow itself,\nper-stage evidence budgeting so the fixer prompt JSON never truncates\nmid-document, dirty-tree and moving-base-ref guards, dead stage\nagents retried instead of ending the loop, and only pure-docs fixes\nskip a full stage re-run."
    },
    {
      "commit": "d2b4d8994eb2a1b8994601069f5180a9c979ee8c",
      "tree": "051cb60bffaf9c191909e36a6f0699bae61d9546",
      "parents": [
        "ded9ad5f4705c9934a3eaea23e1cf92b9b4c3114",
        "e413f05ee6eb6062a8b82c48ed685f0414234497"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 11:41:43 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 28 11:41:43 2026 +0700"
      },
      "message": "Merge pull request #3858 from hathach/claude/validator-done-signals\n\npr-review-validator: done waits for every auto-reviewer to settle"
    },
    {
      "commit": "e413f05ee6eb6062a8b82c48ed685f0414234497",
      "tree": "0f320ca7d56f1042e5d5204c24ca5348a625d010",
      "parents": [
        "42db982e7c6e8ee7d2f80d15a93a7092d3b708c9"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 11:28:15 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 11:28:15 2026 +0700"
      },
      "message": "pr-review-validator: done waits for every auto-reviewer to settle on the head SHA\n\nA cycle running before the bots posted saw zero findings and reported done;\nwith a fast-green CI the babysit loop could exit unreviewed. done now needs\nevery reviewer settled for the current head: Copilot\u0027s verdict review\n(commit_id), Codex\u0027s verdict comment (Reviewed-commit line), its thumbs-up\nreaction on the PR body, or the named claude-review check run — with quota/\nerror notices and the reaction freshness-gated on push time (check-suite\ncreation, not committer date) and every lookup paginated. pr-babysit re-arms\nwith backoff on a pending reviewer instead of exiting unactionable, skipping\nthe pointless final-cycle wait.\n"
    },
    {
      "commit": "ded9ad5f4705c9934a3eaea23e1cf92b9b4c3114",
      "tree": "f92a0047d042c0ee33b5fde5dc2fee7b650c6b1d",
      "parents": [
        "42db982e7c6e8ee7d2f80d15a93a7092d3b708c9",
        "09164de529cdde8aa4f454361a8dcd413bf43b32"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 10:21:49 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 28 10:21:49 2026 +0700"
      },
      "message": "Merge pull request #3861 from hathach/claude/pr-babysit-hil-guard\n\npr-babysit: never edit HIL rig configs without user approval"
    },
    {
      "commit": "09164de529cdde8aa4f454361a8dcd413bf43b32",
      "tree": "f92a0047d042c0ee33b5fde5dc2fee7b650c6b1d",
      "parents": [
        "42db982e7c6e8ee7d2f80d15a93a7092d3b708c9"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 00:14:28 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 28 00:16:26 2026 +0700"
      },
      "message": "pr-babysit: never edit HIL rig configs without user approval\n\nThe workflow\u0027s fix lane once skipped two host tests in test/hil/tinyusb.json\nto green a check whose root cause was a failing fixture drive (reverted in\n4b11d59a4). Rig rosters describe physical hardware: papering over a fixture\nfault hides it from the user who has to swap the part. Now fixAndVerify\nstrips test/hil/*.json from every fix scope (a group left with no other\nfiles is withheld and logged), the code-writer prompt carries the\nconstraint, and ok\u003dfalse keeps such cycles from pushing. HIL stays red\nwhen the fix is a hardware swap - that red is the signal.\n"
    },
    {
      "commit": "42db982e7c6e8ee7d2f80d15a93a7092d3b708c9",
      "tree": "78ee31372172cf2fa48bf062885cd8cbdef6e629",
      "parents": [
        "677182694f555c451eaec0a0d4ee7b14ac532783",
        "9baa97a8c6cc671aefe26168b3d8281da070ebf9"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 15:09:49 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Aug 27 15:09:49 2026 +0700"
      },
      "message": "Merge pull request #3856 from hathach/claude/hil-drop-nt\n\ntest/hil: drop the Windows accommodations, which accommodate nothing"
    },
    {
      "commit": "677182694f555c451eaec0a0d4ee7b14ac532783",
      "tree": "ddfd658b2a85e2fda27dab2124827285b5a45022",
      "parents": [
        "d799b6f572e4b39ebebcf72126d3120f7829c034",
        "92b38fc3b5e9080c96294079e6117d74429f3448"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 14:56:23 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Aug 27 14:56:23 2026 +0700"
      },
      "message": "Merge pull request #3855 from hathach/claude/pr-babysit-split-triage\n\nworkflows/agents: overlap pr-babysit\u0027s review and CI lanes; split pr-monitor; pin agent efforts"
    },
    {
      "commit": "92b38fc3b5e9080c96294079e6117d74429f3448",
      "tree": "268ad55d6fbfe4c50eae96eaa794efeede9e403c",
      "parents": [
        "208f82efe8b9dc7ca3eb210dcbf455a14c97cf3c"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 12:21:04 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 12:21:04 2026 +0700"
      },
      "message": "validate: add claude + codex diff-review stages (opus/high, sol/high)\n\nThe claude stage reviews the diff directly (the code-review skill is a CLI\nbuilt-in, unavailable to subagents); the gate is enforced in-script from\nstructured findings, failing only on confirmed correctness/safety bugs.\n"
    },
    {
      "commit": "208f82efe8b9dc7ca3eb210dcbf455a14c97cf3c",
      "tree": "a31c3ec5d98e67e90ca306f066135110ea25333a",
      "parents": [
        "2b9ddd8b00eb57225fd9caeecd0da1a7954aee06"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 12:21:04 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 12:21:04 2026 +0700"
      },
      "message": "pr-babysit: overlap a fast review lane with the CI watch\n\nReview findings are validated, fixed, and pushed without waiting on CI;\ncheckoutDir decouples the PR checkout from the session cwd. File-less CI\nfailures are scoped by a dedicated agent, paths canonicalized and\nexistence-checked via git ls-files, overlapping groups merged. Per-id\nreply/resolve accounting retries failures and holds the green exit until\nall outward work is drained.\n"
    },
    {
      "commit": "2b9ddd8b00eb57225fd9caeecd0da1a7954aee06",
      "tree": "1d6e9497edaef3196bffdc192e9555ef455343d2",
      "parents": [
        "b15c720ab0a36ee16c1cf7984c7c0f4ccf3a7015"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 12:21:04 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 12:21:04 2026 +0700"
      },
      "message": "agents: split pr-monitor into pr-ci-watcher + pr-review-validator; rename port-dev/driver-reviewer to code-writer/code-verifier; pin model+effort on every agent\n"
    },
    {
      "commit": "9baa97a8c6cc671aefe26168b3d8281da070ebf9",
      "tree": "4adb5cea77de2001fdcfd2e0479abdfa967d7a83",
      "parents": [
        "d799b6f572e4b39ebebcf72126d3120f7829c034"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 01:01:30 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 08:54:07 2026 +0700"
      },
      "message": "test/hil: drop the Windows accommodations, which accommodate nothing\n\nhil_test.py cannot run on Windows and never could: it imports helper.hil_lock,\nwhose module-level `import fcntl` is POSIX-only, so the harness fails at import\nbefore a line of it executes. Past that it reads /sys/bus/usb, /dev/bus/usb,\n/dev/serial/by-id and /proc, kills by process group, and takes flock board\nlocks -- none of which Windows has.\n\nSo the guards were protecting a platform the code cannot reach:\n\n- run_cmd branched three ways on os.name to decide whether to set\n  start_new_session and whether to killpg. The non-POSIX arm called p.kill()\n  instead, which kills only the direct child -- exactly the semantics the whole\n  containment design rejects, since a flasher run through a shell reparents out\n  of reach. Dead code that documented the wrong answer.\n- hil_test picked multiprocessing\u0027s default context on Windows \"so it still\n  IMPORTS there\". It does not import there.\n- test_device_audio_test_freertos returned \u0027skipped\u0027 on nt before touching\n  ALSA, in a function only ever reached from a worker that cannot start there.\n- Seven @unittest.skipIf(os.name \u003d\u003d \u0027nt\u0027) decorators across the two suites.\n  These were the only ones with a real effect -- the unit tests DO import and\n  run on Windows, because they stub pyserial and mostly exercise pure logic --\n  but what they buy is a partially-green suite for a harness that cannot run,\n  and nothing verifies the set is correct: the hil-test hook only ever runs on\n  ubuntu-latest, so a missing guard fails silently until someone tries.\n\nRemoving them makes the POSIX assumption single and explicit rather than\nscattered and half-honoured. Nothing changes on Linux: every removed branch was\nthe one already taken there.\n\nRemoving the run_cmd guards also removes their `else: p.kill()` arms. Those were\nthe Windows branches, and p.kill() reaches only the direct child -- a flasher run\nthrough a shell keeps grandchildren it cannot touch, which is the semantics this\ncontainment design rejects. RunCmdCleanupShape pins what is left: both cleanup\npaths killpg, no try carries an else whose body would run when the kill\nSUCCEEDED, and the BaseException path still re-raises. Structural rather than\nbehavioural because driving a real SIGINT into a blocked communicate() is\ntiming-dependent, and what actually breaks this block is an edit that rebinds a\nbranch -- which is a shape.\n"
    },
    {
      "commit": "d799b6f572e4b39ebebcf72126d3120f7829c034",
      "tree": "788472b0bb45dc9fca9881e44dce36cc3fdf132e",
      "parents": [
        "b15c720ab0a36ee16c1cf7984c7c0f4ccf3a7015"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 27 08:43:02 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Aug 27 08:43:02 2026 +0700"
      },
      "message": "test/hil: run the HID echo in a child, which is the only bound that works (#3852)\n\nhid_generic_inout was the last unbounded blocking IO in the file. hidapi\u0027s\nhidraw backend reads manufacturer/product via udev for each device reaching\ncreate_device_info_for_device, both usb_string_attr served under the device\nlock a wedged usbfs ioctl holds — and every DUT here is VID cafe, so a wedged\nsibling stalls the walk.\n\nA thread cannot bound it: cython-hidapi calls hid_open and hid_close bare\n(0.15.0 hid.pyx), so they hold the GIL and the waiter can never resume.\nMeasured — a 1.0s bound never returned. run_cmd\u0027s killpg reaches a child\nregardless; it gains an argv form for the -c body.\n\nFilters on both ids: hidapi only runs the free uevent pre-check when ids are\npassed (linux/hid.c:962), so an unfiltered walk sends every device straight to\nthe locked reads. Tests stall via ctypes.PyDLL, which unlike CDLL holds the\nGIL — the shape a thread bound cannot cover."
    },
    {
      "commit": "b15c720ab0a36ee16c1cf7984c7c0f4ccf3a7015",
      "tree": "a66805655d019251c30a2d3e4affa37f07ae9731",
      "parents": [
        "375e20090a0a60fd2d47ba6553d189e05482dfea"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 26 13:41:05 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 26 13:41:05 2026 +0700"
      },
      "message": "test/hil: make main() readable and stop the suite sleeping (#3848)\n\nThree readability changes with no behaviour change on the healthy path —\nevery pre-existing test passes untouched.\n\nmain() was 368 lines with try/finally three deep, its two abort paths\nnear-identical 40-line blocks; _abort_report holds that shape once, and the\ncontroller-hint cache and pool construction move to their own helpers.\n368 -\u003e 279, test_board 180 -\u003e 151, test_device_usbtest 164 -\u003e 125.\n\ntest_hil_bounded.py cost 78s on every commit under test/hil/, mostly one 3s\npost-flash settle paid by ten tests against a fake rig. Now 37s.\n\nFixes two pre-existing defects the extraction exposed: _write_failed_spec was\nunguarded inside the abort path, so an OSError there replaced the caller\u0027s\nRuntimeError and no report was written at all; _save_controller_hints overlaid\na startup snapshot onto the re-read cache, clobbering a concurrent job\u0027s newer\nvalues. Also five comments that stated the opposite of the code, and both table\nrenderers measuring width with len() against two-column status marks."
    },
    {
      "commit": "375e20090a0a60fd2d47ba6553d189e05482dfea",
      "tree": "9956cf954acc3d1f4b35f6866724d44e3277c355",
      "parents": [
        "60194f8d10ebc9ec1aeba559e359ebe3ca9b756b"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 25 15:04:42 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Aug 25 15:04:42 2026 +0700"
      },
      "message": "hil: make hil_report.md a rendering of hil_report.json (#3840)\n\nhil_report.json and hil_report.md were written independently. Four writers\nproduced the markdown and three wrote no JSON at all -- and those three are the\npaths where a run died, so a JSON consumer saw nothing exactly when it mattered:\nthe per-board verdicts an agent hands back reported the whole fleet as \"no\nreport row\" while a human read the real story from the markdown.\n\nEvery writer now goes through render_report(), so a table can never contain\nsomething the JSON does not. The document gains `scope` (a three-board PR run\nand a full run that lost 24 boards were indistinguishable) and `caveat` (how the\nrun ended). `banner` carries rig health across an --accumulate retry; `caveat`\nrecords how a run ended and must not -- conflating them made a clean retry\npublish an abandonment that never happened.\n\nhelper/hil_report.py owns the document end to end, dissolving the import cycle\nthat forced write_timeout_report to compose its own markdown and removing a\nduplicate cell classifier kept in sync by hand. hil_summary.py is deleted; its\nCLI moves there. hil_ci.sh uploads the sidecar so a remote --accumulate has a\nmerge bas"
    },
    {
      "commit": "60194f8d10ebc9ec1aeba559e359ebe3ca9b756b",
      "tree": "1620c48a7c46ccd58c757ee44c80f99e86947495",
      "parents": [
        "b610ff039bafa1040c19d6a11cb04adcb22936e5"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 25 10:50:36 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Aug 25 10:50:36 2026 +0700"
      },
      "message": "README sponsor list and triage labels (#3842)"
    },
    {
      "commit": "b610ff039bafa1040c19d6a11cb04adcb22936e5",
      "tree": "65a11c037eda04b135291762c1a9d7b497726adb",
      "parents": [
        "da255b1d2db10b8f31332a779b2a526f579acee1"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 25 10:35:45 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Aug 25 10:35:45 2026 +0700"
      },
      "message": "ci_select: fix the membrowse test\u0027s env dependence, and stop HIL unit tests taking the rig (#3846)\n\ntest_the_upload_board_can_diverge_from_the_built_board called\nget_family_boards without ci\u003dTrue, so it pinned the developer\u0027s set, not the\nrunner\u0027s: the CI skip lists move the one-first pick on three families. It\nheld locally and went red on its first CI run. Pass ci\u003dTrue, as\n_prune_buildable already does, and pin the runner\u0027s twelve.\n\nRule 2 is a bare test/hil/ prefix, so the harness\u0027s own unit tests booked the\nfull 27-board rig for diffs that cannot reach it. Carve test/hil/test/** out\nto rule 1b, beside test/{fuzz,unit-test}/**; the harness itself is untouched.\nA test pins that directory\u0027s file list, so anything added there that the rig\ndoes read fails rather than silently skipping hardware. Rule table updated in\nthe spec and its carbon in the docstring."
    },
    {
      "commit": "da255b1d2db10b8f31332a779b2a526f579acee1",
      "tree": "154c8b8b9cf48f4a111edd385b8df7cc6919f3b8",
      "parents": [
        "aa0f4a40b59012e6ab421949c153088ca4984df4"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 25 09:46:42 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Aug 25 09:46:42 2026 +0700"
      },
      "message": "ci: an empty selection must build nothing, plus selector follow-ups (#3845)\n\nci: an empty selection must build nothing, plus selector follow-ups\n\nA PR whose build axis legitimately selected nothing rebuilt everything.\nbuild.yml reads .build.families twice - as a |-joined regex, and implicitly\nas \"is anything selected\" - but tested only -z \"$FAMILY_REGEX\", which an\nempty list and a charset-rejected one both satisfy while meaning opposite\nthings. ci_set_matrix had already returned the correct all-empty matrix;\nthe fall-open branch discarded it. #3842 and #3840 each spent 74 cmake legs\non it. Branch on the two cases instead, rename FAM_* to FAMILY_*, and cover\nthe block with a test that extracts it from build.yml and executes it - it\nhad no test at all, which is how this shipped through two merges.\n\nFollow-ups to the same machinery: glob.escape the repo root at five sites,\nso a checkout path containing [ or * stops failing closed; drop the ci-full\nlabel, read after the matrix was already computed and so never functional;\ndelete 13 mcu:MKL25ZXX / mcu:SAME5X skip tokens matching no board; carry the\nrule table in the module docstring, guarded against drift; and pin six\nselection behaviours a mutation pass proved untested.\n\nCut the selector\u0027s cost 1.8x (26.0s -\u003e 14.6s) with 0 divergences over 260\npaths, and stop scoping the membrowse upload by the PR example filter."
    },
    {
      "commit": "aa0f4a40b59012e6ab421949c153088ca4984df4",
      "tree": "4f4e06b386205185ea49e9af9586b6c7206c625a",
      "parents": [
        "fd70160a2f5fd23de1abfbaefb6399746a90b588",
        "050595d64f9f130783853a2342eb1114d32199e8"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Sun Aug 23 10:42:53 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Aug 23 10:42:53 2026 +0700"
      },
      "message": "Merge pull request #3843 from hathach/claude/ci-select-rule17\n\nci_select: classify the files that were reaching rule 17"
    },
    {
      "commit": "050595d64f9f130783853a2342eb1114d32199e8",
      "tree": "4f4e06b386205185ea49e9af9586b6c7206c625a",
      "parents": [
        "6ff0ef97702c0e6b6d17b7a8fe856b31164efe57"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Sat Aug 22 23:10:14 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Sat Aug 22 23:10:14 2026 +0700"
      },
      "message": "ci_select: address Copilot review - anchor _META_RE, cover rule 12b\n\nAnchor the .github file alternatives. FUNDING.yml, labeler.yml and\nmembrowse_pr_message.j2 sat inside a group whose only `$` belonged to the\nworkflows/ branch, so they matched as prefixes: .github/labeler.yml.bak and\n.github/FUNDING.yml.old were classified as metadata and would have selected\nnothing. No such file exists today - the workflows/ alternative was already\nanchored and ISSUE_TEMPLATE/ is a directory prefix on purpose.\n\nRule 12b had no test of its own: TestNoTrackedFileIsUnclassified only proved\nsrc/typec no longer reaches rule 17, not that the answer is right. TestTypecRule\npins it - non-full, every selected example under typec/, all four src/typec files\nanswering alike, no rig board, and the set derived from CFG_TUC_ENABLED rather\nthan hardcoded, so it follows a new typec example on its own. Verified all four\nfail with rule 12b removed.\n"
    },
    {
      "commit": "6ff0ef97702c0e6b6d17b7a8fe856b31164efe57",
      "tree": "ae456b01bc223be69db574be753c191c92abb83a",
      "parents": [
        "03a329eeda09e073d7de9be84df55d65392e4013"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 23:14:25 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 23:14:25 2026 +0700"
      },
      "message": "build_utils: key the caches on the tree, not just the arguments\n\nThe eight lru_cache layers take repo-RELATIVE paths - \u0027hw/bsp/\u003cfam\u003e\u0027,\n\u0027examples/\u003cex\u003e/skip.txt\u0027, the literal \u0027hw/bsp\u0027 glob - while ci_select._in_repo()\nchdirs around every call so one process can classify more than one tree. With no\ncwd in the key the second tree gets the first tree\u0027s answers.\n\nReproduced: skip_example(\u0027host/bare_api\u0027,\u0027metro_m0_express\u0027) is False at the repo\nroot and STILL False after chdir into a tree where that board does not exist; only\ncache_clear() gave the right answer. It bites the code-size skill\u0027s base-vs-branch\nworktree compare, /pre-pr, and the first test that points classify_build at a\nfixture tree. Master had no caching here, so the hazard arrived with it.\n\n_cwd_cache puts os.getcwd() in the key. The 199-test suite passed before only\nbecause every test happens to pass the real REPO; the new\nTestCachesAreKeyedOnTheTree crosses trees deliberately.\n\nAlso adds the drift guard the class rule was missing. Ports, hw/mcu, get_deps\ntokens and bsp families each have one; the class rule had only a comment claiming\nvendor_host.c was the sole \"enabled by no example config\" case until its removal -\nwhich src/class/bth falsifies today. TestClassesWithNoEnablingExample pins the set\nto {bth}, so a class added before its first example, or an example config flipped\nto 0, fails here instead of silently selecting nothing on both axes. Verified it\nfires by adding a class dir nothing enables.\n"
    },
    {
      "commit": "03a329eeda09e073d7de9be84df55d65392e4013",
      "tree": "b59db5c7ecb4dc5c9f1e87d35634c9fc58a3d462",
      "parents": [
        "fd70160a2f5fd23de1abfbaefb6399746a90b588"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 22:17:51 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 22:17:51 2026 +0700"
      },
      "message": "ci_select: classify the 254 files that were reaching rule 17\n\nRule 17 (unclassified -\u003e full on both axes) is the fail-open net for paths nobody\nanticipated, and it must stay that way: a wrong `full` costs runner minutes and is\nvisible in the run, a wrong `empty` costs a merged regression and is invisible.\nBut nothing in the tree should REACH it, and 254 tracked files did.\n\nThe cost was real. PR #3842 changed a skill, a README and .gitignore; .gitignore\nmatched no rule, so both axes went full and 74 cmake legs span up runners to do\ncheckout + toolchain + get_deps before skipping the build, plus the whole 30-board\nrig. Three changes, none of which touch rule 17 itself:\n\n1. _META_RE - repo metadata and tooling no Build step reads: .gitignore,\n   .gitattributes, .clang-format, .codespellrc, .pre-commit-config.yaml,\n   .readthedocs.yaml, .PVS-Studio/, .idea/, sonar-project.properties, the\n   packaging manifests, CMakePresets, udev rules, test/{fuzz,unit-test} (their own\n   jobs build those), the non-build .github/ files, and the tools/*.py scripts no\n   build invokes. Deliberately NOT included, and still full: .circleci/**,\n   .github/workflows/build*.yml, .github/actions/**, .github/scripts/**. The line\n   is \"does a Build step read this\", not \"is it source\".\n\n2. Rules 15 and 16 now match what they already claimed. Row 15 names\n   examples/\u003crole\u003e/CMakeLists.txt and the regex never had it; row 16 says\n   tools/build*.py but anchored tools/build\\.py$. Both got the right answer only\n   because rule 17 caught them on the way past. Also names their siblings -\n   family_support.mk, family_rules.mk, src/CMakeLists.txt, src/tinyusb.mk - and\n   .circleci/**, which generates the whole CircleCI matrix and was in no row at all.\n\n3. src/typec/** gets row 12b. It is listed unconditionally by both build systems\n   but its body is `#if CFG_TUC_ENABLED`, which only examples/typec/power_delivery\n   sets - the same shape as the class rule, so the same answer: the examples that\n   enable it (stm32g4 and stm32u5 after the buildability prune), and nothing on the\n   rig, which runs no typec test. It was force-fulling 82 families and all 30 boards.\n\nTestNoTrackedFileIsUnclassified walks every tracked file and asserts none reaches\nrule 17, on both axes - 254 -\u003e 0. Verified it fails when a new unclassified path\nappears. That turns 17 into what it should be: unreachable for anything in the\ntree, so it fires only for genuinely new shapes, and the author is told to write\nthe row rather than letting the fall-through pick an answer for them.\n\ntest_full_paths used sonar-project.properties as its stand-in for \"unclassified\";\nthat is now metadata, so the case moved to the new\ntest_repo_metadata_is_not_a_build_input, with test_the_build_machinery_is_still_full\npinning the other side of the line.\n"
    },
    {
      "commit": "fd70160a2f5fd23de1abfbaefb6399746a90b588",
      "tree": "d65fb7848c2365b45279a6727f001022f2ba7507",
      "parents": [
        "cb3caee74de7dbb9fc91a5fdb37b448a63eadae0",
        "c8e0e94d889cd9b9a423d36c2b194d50805d8272"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 17:24:35 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 21 17:24:35 2026 +0700"
      },
      "message": "Merge pull request #3841 from hathach/build-filter\n\nci: scope the build matrix and HIL run to what a PR affects"
    },
    {
      "commit": "c8e0e94d889cd9b9a423d36c2b194d50805d8272",
      "tree": "d65fb7848c2365b45279a6727f001022f2ba7507",
      "parents": [
        "f17be6770b601a32bdfcc1459e8851becf7fea84"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 17:04:34 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 17:04:34 2026 +0700"
      },
      "message": "examples: keep CFG_TUH_VENDOR 0 in tusb_config.h\n\nRemoving the obsolete host vendor driver also dropped the `#define CFG_TUH_VENDOR 0`\nline from the six example configs that carried it. Put it back: host vendor is\ncoming, and the configs are where a reader looks for the set of host classes an\nexample can turn on.\n\nRestored byte-identical to the pre-removal state, each file keeping its own column\nalignment. The define is inert today - nothing under src/, hw/ or tools/ reads\nCFG_TUH_VENDOR - and it is 0 everywhere, so ci_select still reads the vendor class\nas enabled by no example and a change to it still selects nothing.\n\nNote the option\u0027s default in src/tusb_option.h is still gone; implementing the\ndriver will need that back alongside the usbh driver-table entry.\n"
    },
    {
      "commit": "f17be6770b601a32bdfcc1459e8851becf7fea84",
      "tree": "b0225fcdce89e4fe73b3b8bfb8f787d7ab48afd9",
      "parents": [
        "a408a8e9af4a043202f79a2b8e20d229093148e5"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 16:08:25 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 16:08:25 2026 +0700"
      },
      "message": "ci_set_matrix: fall open when no selected family builds anywhere\n\nfamily_list maps a family to the toolchains that build it, and seven hw/bsp\nfamilies are in neither: cxd56, efm32, espressif, f1c100s, pic32mz, py32f0,\nsame7x. Scoping to one of them intersected to nothing, so every toolchain key was\n[], every cmake leg skipped on `if: inputs.build-args !\u003d \u0027[]\u0027`, code-metrics took\nits no-metrics branch, and the PR went green from a build job that ran no\ncompiler. The only signal was a stderr line nothing greps for.\n\nNot a coverage regression - master gave the same diff no compile coverage either,\nsince none of the other families compiles same7x\u0027s board.h. What is new is that\nthe gap used to be masked by the full matrix and is now the whole answer, and\nthat green now means \"ran no compiler\" rather than \"compiled 64 families\".\n\nA selection whose families ALL miss is now unusable rather than empty: it prints\nUNSCOPED, which build.yml and .circleci/config.yml already grep to drop the build\nextras with it, and emits the full matrix. The two neighbouring cases keep their\nown answers - an explicit families: [] is still a legitimate nothing-selected, and\na partial miss still scopes to the families that do build, noting the rest.\n\nThe contract test pinned an exact count of fall-open markers, which this would\nhave broken; it now pins the invariant (every message that emits the full matrix\ncarries the marker) and was checked to still fail when a marker is removed.\n\nAlso corrects the drift guard\u0027s note about espressif: hil-build-esp builds its\nboards by name, but that job is gated on repository_owner, so on a fork an\nespressif-only PR builds nowhere.\n"
    },
    {
      "commit": "a408a8e9af4a043202f79a2b8e20d229093148e5",
      "tree": "b2a122bfaf08e7e16f3a6315fe54e1d08a0f86ce",
      "parents": [
        "e13eff8d4e757ebe7709a58fce44017b8be5a84d"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 14:23:40 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 14:23:40 2026 +0700"
      },
      "message": "hil: express a board\u0027s always-on defines as a variant, dropping build.args\n\nThe roster had two ways to pass a cmake -D to a board\u0027s build: `build.args`,\napplied to every variant, and `variant[].defines`, applied to one. They did the\nsame thing, and only metro_m4_express used the first - for MAX3421_HOST\u003d1, which\nis what makes it the one rig board that compiles hcd_max3421.c.\n\nA board whose define is always on now carries a single variant named after itself,\nwhich is exactly the shape `board.get(\u0027variant\u0027) or [{\u0027name\u0027: name, \u0027flags\u0027: \u0027\u0027}]`\nalready synthesises everywhere - so the build dir, the HIL report row and the\nvariant-boundary handling are unchanged. raspberry_pi_pico has used that shape\nfor its flags all along.\n\nRemoves the BuildCfg type and the parallel code path from all four consumers:\nhil_test.build_board, hil_pool_check\u0027s two builders, hil_ci_set_matrix and\nci_select.board_options.\n\nVerified: the hil-build matrix entry is byte-identical\n(`-b metro_m4_express -DMAX3421_HOST\u003d1`), hil_test\u0027s build command is unchanged,\nci_select still selects the board for a max3421 diff with MAX3421_HOST in its\noptions, and a real build of dual/host_info_to_device_cdc and host/cdc_msc_hid on\nthat board still compiles hcd_max3421.c.\n"
    },
    {
      "commit": "e13eff8d4e757ebe7709a58fce44017b8be5a84d",
      "tree": "32073e11437e057d92ff843290d3a5594c86d528",
      "parents": [
        "f96ddbaa1e11a98f8076df48ba73026191c28399"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 12:41:47 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 12:41:47 2026 +0700"
      },
      "message": "ci: fix nine ways the selection under-selected or mismatched\n\nEvery one of these dropped coverage silently - the worst failure mode here,\nbecause the PR still goes green. Found by review, each reproduced first.\n\nSelection rules:\n\n* class_macros derived the config macro from the class DIRECTORY, so a change to\n  src/class/midi/midi2_device.c selected the midi_test examples (which do not\n  compile it) and never examples/device/midi2_device (the only one that enables\n  CFG_TUD_MIDI2, and the only one that does). The file\u0027s own macro is unioned in\n  where it differs - union, never replace: over-selecting costs a build,\n  under-selecting merges a break.\n* the ${FAMILY_MCUS} fallback added for espressif fired on any family whose\n  _family_mcus came back empty, and _cmake_sets is if()-blind and keeps the FIRST\n  definition - so mcx/frdm_mcxn947 answered MCXA15, a token six examples\u0027 skip.txt\n  names, dropping 12 firmware images CMake builds. Limited now to families that\n  never spell set(FAMILY_MCUS ...) at all.\n* lib_examples read only an example\u0027s top-level CMakeLists.txt/Makefile;\n  host/msc_file_explorer_freertos names lib/embedded-cli in src/CMakeLists.txt and\n  survived by luck. The whole example tree is scanned. (SEGGER_RTT and rt-thread\n  still resolve to nothing: all three references sit inside a LOGGER\u003drtt guard no\n  CI build sets - the documented ruling, not a miss.)\n* get_family_boards applied ci_skip_boards/ci_preferred_boards only under\n  GITHUB_ACTIONS/CIRCLECI, so the selector answered differently on a laptop than\n  on a runner; _prune_buildable forces CI semantics. Its one-board pick also\n  abandoned the whole preferred list when entry one could not build the -e set,\n  and asked skip_example without the build\u0027s -D tokens.\n* _config_enables and lib_examples still read with the locale encoding - under\n  LC_ALL\u003dC the selector tracebacked on three tracked tusb_config.h files. The\n  whole selector and its suite run clean there now.\n\nWorkflows:\n\n* the Membrowse Upload step omitted $EX_ARGS, but --one-first now picks the board\n  from the -e set, so it configured a different, empty build dir and uploaded\n  --identical for a board never compiled. It takes $EX_ARGS for the BOARD; the\n  target stays the aggregate, which has no DEPENDS and still records every example.\n* blanking FAM_REGEX reset only build_filtered, leaving the build scoped while\n  code-metrics took the UNSCOPED branch and diffed a 1-family run against the full\n  averaged baseline. All three drop together now, as CircleCI\u0027s fall-open does.\n* CircleCI\u0027s EX_ARGS had no character screen and is used unquoted, and its\n  code-metrics job still exit 1\u0027d on an empty metrics set - which a scoped build\n  makes a legitimate outcome.\n* a `ci-full` PR label now turns the scoping off for one PR. A selector bug\n  under-selects silently, and without a label the only ways back to a full matrix\n  are accidental.\n\nPerformance, since the selector gates every other job: family.cmake texts are read\nonce rather than per changed directory (a 6,000-file dep bump re-read 84 files\n99,892 times) and _scrape_mcu is cached: 2.2s -\u003e 0.29s there, 0.8s -\u003e 0.33s on a\nclass diff.\n\nTests: a drift guard for hw/bsp families absent from ci_set_matrix.family_list\n(they select zero legs now, where they used to ride the full matrix); the rule-4\nport test asserted a SUBSET, which set() satisfies, so it could not fail on the\nempty selection it exists to catch; the GITHUB_ENV guard test counted a SUM of two\nguards. Drops metrics.py\u0027s --only-examples, which nothing called, and applies the\nTOTAL scrub to the by-example branch that skipped it.\n"
    },
    {
      "commit": "f96ddbaa1e11a98f8076df48ba73026191c28399",
      "tree": "52d56a27ca8a17bcba179f735d26ca70f22182fa",
      "parents": [
        "04d0f71984117b8c72349f4584bd9e26a37b129c"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:09:03 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:09:03 2026 +0700"
      },
      "message": "docs: record the CI selection design and its plan\n\nThe binding rule table (17 rows x 3 answer columns), the measured effect per PR\nshape, and the reasoning behind the parts that look surprising: why empty means\nempty, why hw/mcu and lib are rules rather than full-matrix paths, why get_deps.py\nis diffed as data, and which build system is the reference. The plan is the\ntask-by-task record of how it was built, kept as the origin trail.\n"
    },
    {
      "commit": "04d0f71984117b8c72349f4584bd9e26a37b129c",
      "tree": "1d87e53f8b3b6f94da3fcb03a5da5299c7cd815f",
      "parents": [
        "696c7807f543a6c55656d81a8f6d8969584e9614"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:07:27 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:07:27 2026 +0700"
      },
      "message": "ci: scope the build matrix and the HIL run to what a PR affects\n\nEvery PR built all 74 legs (2494 example builds on GHA cmake alone) and flashed\nall 30 rig boards, whatever it touched. One classifier now walks the PR diff twice\nand answers three questions: which families to build, which examples per family,\nand which boards run which tests. Fail-open throughout - anything no rule\nclassifies, any exception, any unusable output falls back to the full matrix, and\na master push always builds everything.\n\ntest/hil/helper/hil_select.py moves to tools/ci_select.py: it is no longer HIL-only,\nand tools/ is where the build side can import it. test_hil_select.py follows it as\ntest_ci_select.py.\n\nRules (docs/superpowers/specs/2026-08-19-ci-build-family-filter-design.md holds the\nfull table): a port selects the families whose family.cmake references it, and its\nrole - a dcd change skips host examples and vice versa; a class selects only the\nexamples whose tusb_config.h enables its CFG_TU[DH]_ macro, following cross-class\nincludes; an example selects itself; hw/bsp selects its family or board; hw/mcu and\nlib select whoever references them. CMake is the reference for all of it - make\nfollows whatever cmake decides, family.mk is never scanned.\n\nEmpty means empty (maintainer ruling): a rule that classifies a path to nothing\nselects nothing. Ports no family references, classes no config enables, libs no\nexample builds and hw/mcu paths that resolve nowhere are all real - nothing\ncompiles them, so nothing can validate them, and the master-push build is the net.\nStructural tests pin each such case with an explicit allowlist, so the day one\nstops being empty it fails pre-commit instead of silently narrowing CI.\n\nPer-example builds: build.py grows a repeatable -e, resolved against the targets\nCMake actually registered and batched into one `cmake --build --target a b c`.\nbuild_utils mirrors CMake\u0027s family_filter (the whole FAMILY_MCUS list, ${...} and\nstring(TOUPPER ...) resolved) for the cmake side, while the make side keeps\nmaster\u0027s algorithm verbatim - the two build systems answer differently and a shared\nanswer breaks lpc54\u0027s make link. hil-build gains this even on a full selection:\n1702 example builds become 515.\n\nTransport: the selection travels as a file, never an argv or env var - a mass-sweep\ndiff selects 261 KB against a 128 KiB exec limit, and E2BIG would fail the step\nbefore its own fallback could run. CircleCI carries the example map inside the\ngenerated config (pipeline parameters cap at 512 chars), swapped into the parameter\ndefaults by sentinel match, and drops the scoping wholesale if that rewrite fails.\nEvery PR-derived value written to $GITHUB_ENV/$GITHUB_OUTPUT is character-screened.\n\nCode metrics follow the scoping: metrics.py emits per-example totals, and\nmetrics_pair_compare compares the (board, example) pairs present on both sides\ninstead of a scoped run against a full-matrix average.\n\nThe selector\u0027s own suite gates it in both providers: a selector that exits 0 with\nvalid-but-wrong JSON is the one failure fail-open cannot catch, so a red suite\nmeans the full matrix.\n"
    },
    {
      "commit": "696c7807f543a6c55656d81a8f6d8969584e9614",
      "tree": "b58cb5c29cb19be6e602a8bb869344db3b51029b",
      "parents": [
        "b646a9decfcac5345b77c74b6b0e2d217add756f"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:06:55 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:06:55 2026 +0700"
      },
      "message": "get_deps: correct two family tokens that matched nothing\n\nget_deps matches a family token against a requested family name verbatim\n(`f in deps_optional[d][2].split()`), so a token naming no hw/bsp directory makes\nits entry unreachable:\n\n  hw/mcu/allwinner said \u0027fc100s\u0027; the family is hw/bsp/f1c100s, and\n    f1c100s/family.cmake sets SDK_DIR to ${TOP}/hw/mcu/allwinner/f1c100s\n  hw/mcu/sony/cxd56/spresense-exported-sdk said \u0027spresense\u0027 (the SDK\u0027s name);\n    the family is hw/bsp/cxd56, whose family.cmake points SDK_DIR at it\n\n`python3 tools/get_deps.py f1c100s` and `... cxd56` now fetch the SDK each of those\nfamilies builds against; before, both printed \"no additional dependencies found\".\ndocs/reference/dependencies.rst is generated from deps_all by tools/gen_doc.py, so\nit is updated to match - column widths are unchanged (the widest cell is\nlib/CMSIS_5\u0027s, untouched) and every row was cross-checked against deps_all.\n"
    },
    {
      "commit": "b646a9decfcac5345b77c74b6b0e2d217add756f",
      "tree": "b5d6297a3847b67ae3846dcf943847e5ded0fbb7",
      "parents": [
        "565263ef1c8744232561ce297dabbdff3e3d284c"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:06:38 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:06:38 2026 +0700"
      },
      "message": "examples: restore host builds on samd2x_l2x\n\nNine examples/host/*/only.txt gate on family:samd21, but 2a8811ebb merged the\nsamd2x and saml2x BSPs into hw/bsp/samd2x_l2x. skip_example takes `family:` from\nthe directory name, so since that rename every one of these examples has been\nskipped on every board of the family, under make as well as cmake - although\nhw/bsp/samd2x_l2x/family.cmake wires src/portable/microchip/samd/hcd_samd.c.\n107 host firmware images were being compiled nowhere.\n\nThe merged family is wider than the old samd21 one, so three boards need an\nexplicit skip rather than the rename alone:\n\n  atsaml21_xpro, saml22_feather, sensorwatch_m0 - not samd21, so hcd_samd.c is\n    not compiled for them (previous commit); SAML22 has no host controller at all\n  curiosity_nano - SAMD21 with 16 KB RAM; msc_file_explorer_freertos overflows it\n    by 3688 bytes (ram 122.51%). Only that one example; the other eight fit.\n\nVerified across the whole family: cmake 11 boards x 9/9 examples + curiosity_nano\n8/9, three boards skipped, 0 failures; make 98 OK / 0 failed (was 98/9 before).\nDevice examples on saml21 and saml22 are unaffected.\n"
    },
    {
      "commit": "565263ef1c8744232561ce297dabbdff3e3d284c",
      "tree": "9703a7269abbb44e7cd66e61ac6cd8e01171034f",
      "parents": [
        "a57f857f811e054e7a240fc55520648192349c2b"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:06:26 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:06:26 2026 +0700"
      },
      "message": "samd2x_l2x: build hcd_samd.c for samd21 only, as family.mk already does\n\nfamily.cmake listed src/portable/microchip/samd/hcd_samd.c twice: once\nunconditionally, and once inside `if(SAM_FAMILY STREQUAL \"samd21\")` under the\ncomment \"Add HCD support for SAMD21 (has host capability)\". The unconditional copy\ndefeated the gate, so cmake compiled the host controller driver for saml21 and\nsaml22 while family.mk compiled it for samd21 alone - and SAML22 has no host\ncontroller at all (hcd_samd.c fails there with `unknown type name\n\u0027UsbHostDescriptor\u0027`).\n\nNothing built the host examples on this family, so the divergence was invisible;\nthe next commit makes it matter.\n"
    },
    {
      "commit": "a57f857f811e054e7a240fc55520648192349c2b",
      "tree": "0ca064dcc0d3f12974e12303ffb151ca98b75115",
      "parents": [
        "cb3caee74de7dbb9fc91a5fdb37b448a63eadae0"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:06:14 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 11:06:14 2026 +0700"
      },
      "message": "vendor: remove the obsolete host vendor driver\n\nvendor_host.c/.h implemented a CFG_TUH_VENDOR class driver that no example, board\nor test ever enabled: usbh\u0027s driver table entry was compiled out everywhere, and\nthe six tusb_config.h files that mentioned the macro all set it to 0. Maintainer\ncall - dead code, not a shrinking of supported classes.\n\nRemoves the sources, the usbh driver-table entry, the CFG_TUH_VENDOR default in\ntusb_option.h, the tusb.h include, both build-system source lists, the rp2040\nfamily.cmake entry and the IAR project template rows.\n"
    },
    {
      "commit": "cb3caee74de7dbb9fc91a5fdb37b448a63eadae0",
      "tree": "ebb5e40aa6774417ad2d3f2f8968dd17c78b83ba",
      "parents": [
        "9466f3cda69b052679e7bb078b5cf7906ddbea27",
        "872b4fbdc3c607b522211839a6b3b82e18310a1b"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 01:03:05 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 21 01:03:05 2026 +0700"
      },
      "message": "Merge pull request #3837 from hathach/hil-setup-writeup\n\ndocs: add hardware-in-the-loop rig reference"
    },
    {
      "commit": "872b4fbdc3c607b522211839a6b3b82e18310a1b",
      "tree": "ebb5e40aa6774417ad2d3f2f8968dd17c78b83ba",
      "parents": [
        "9466f3cda69b052679e7bb078b5cf7906ddbea27"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 14:30:59 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 21 00:47:27 2026 +0700"
      },
      "message": "docs: add hardware-in-the-loop rig reference\n\nDocument the ci and hfp HIL rigs in enough detail to reproduce one: bill of\nmaterials with photos, BIOS/IOMMU and vfio-pci passthrough on the Proxmox\nhost, the Renesas uPD720201 firmware install, the guest software and\npermissions, the one-hub-per-root-port USB topology rule and the per-box\nsplit of probe and DUT hubs, how CI drives the rigs, and the operational\ngotchas.\n\nThe attached-board table is generated from test/hil/tinyusb.json and\ntest/hil/hfp.json by tools/gen_doc.py into docs/reference/hil_boards.md,\nwhich the page includes. Sphinx excludes that partial so it is not also\nbuilt as an orphan document.\n\nAlso exclude docs/superpowers/ from the Sphinx build: it holds internal\nplans, specs and handoffs rather than published documentation, and since\nnothing references them from a toctree each emitted \"document isn\u0027t\nincluded in any toctree\" -- 26 warnings in total, so build_doc.py -W could\nnever pass. It now does.\n"
    },
    {
      "commit": "9466f3cda69b052679e7bb078b5cf7906ddbea27",
      "tree": "d1cc160b3a0c34c96869a407c86e0e080c373bf1",
      "parents": [
        "7800876bf151a239046521232dc4603b156061be",
        "0fa0ece024fecae0847459b5949c66f40fcc6e11"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 22:49:07 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Aug 20 22:49:07 2026 +0700"
      },
      "message": "Merge pull request #3836 from hathach/claude/hil-doc-audit\n\nhil: one-run scheduling with JSON result handoff; audit and correct the .claude instruction surface"
    },
    {
      "commit": "0fa0ece024fecae0847459b5949c66f40fcc6e11",
      "tree": "d1cc160b3a0c34c96869a407c86e0e080c373bf1",
      "parents": [
        "208ce50e0d5c08eead9c854d73fd397748e1413e"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 18:30:45 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 18:30:45 2026 +0700"
      },
      "message": "hil: address Copilot review — loud extraction markers, exit-visible variant warnings\n\nThe workflow-logic harness slices hil-validate.js between marker strings (the body is not\na module; the runtime wraps it, so markers are the only handle). A renamed marker used to\nproduce a garbage slice and a confusing ReferenceError; it now fails naming the missing\nmarker, proven by mutating the marker and watching the message.\n\nThe variant-warning loop in hil_ci.sh read variant_names through a process substitution --\nthe exact exit-status blindness the comment in resolve_build_dirs warns about, two\nfunctions earlier in the same file. A plain command-substitution assignment is visible to\nset -e, so a malformed roster now aborts instead of silently skipping the warnings.\n"
    },
    {
      "commit": "208ce50e0d5c08eead9c854d73fd397748e1413e",
      "tree": "b87782ece53a9f86031effa6d7f949b8cb77fda6",
      "parents": [
        "21d7332d5884e0abbf9e3f3762deefa589564783"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 17:50:46 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 17:50:46 2026 +0700"
      },
      "message": "docs: name the report-unification handoff after its PR\n\nThe doc carried its own rename instruction for when the branch gained a number;\nthe branch is PR #3836.\n"
    },
    {
      "commit": "21d7332d5884e0abbf9e3f3762deefa589564783",
      "tree": "c26545eb78e8c60d1a49f4281b2e5b91f941132c",
      "parents": [
        "198f5ce623646574129256218e60c1fc68238fc7"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 17:43:44 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 17:43:44 2026 +0700"
      },
      "message": "hil, docs: reference toolchains by their official env vars, not one rig\u0027s paths\n\n~/code/pico/pico-sdk and $HOME/code/esp-idf/export.sh are the ci rig\u0027s private layout;\nwritten into instructions they silently stop being true on tusb, a dev PC, or any\nfuture rig. The docs now use the variables the vendors define -- PICO_SDK_PATH for the\nPico SDK and IDF_PATH for ESP-IDF, activated explicitly as `. \"$IDF_PATH/export.sh\"` --\nand leave where the checkouts live to each host\u0027s profile.\n\nThe variables are only useful if the shells that agents actually get can see them, and\n`ssh \u003crig\u003e \u0027cmd\u0027` is non-interactive AND non-login: it reads no profile, and Debian\u0027s\nsshd-sourced ~/.bashrc returns at the interactive guard before most of the file. The ci\nrig already keeps its exports in the section ABOVE that early-return; IDF_PATH now sits\nthere beside PICO_SDK_PATH, and the whole chain is verified from a plain non-interactive\nssh: both variables visible, `. \"$IDF_PATH/export.sh\"` activates ESP-IDF v5.5.3 with\nidf.py on PATH -- no login shell, no alias, no hard-coded path. hil-pool-check documents\nthat placement so the next rig is set up the same way.\n"
    },
    {
      "commit": "198f5ce623646574129256218e60c1fc68238fc7",
      "tree": "1ed0590898fc178e8336d84d6785685ebededd36",
      "parents": [
        "c514a6ee91a53927f55ba35e0a2933eddb5cefeb"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 16:47:23 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 16:47:23 2026 +0700"
      },
      "message": "docs: hand off unifying the HIL report\u0027s two artifacts\n\nFour writers produce hil_report.md and three of them write no JSON - the no-boards\nexit, the pool-guard fallback and _abandon_exit\u0027s text prepend. Those are exactly the\nruns that failed, so hil_summary.py, which builds an agent\u0027s per-board verdicts from\nthe sidecar, sees nothing while a human reading the markdown sees the real story. The\nscope note is markdown-only too, so a three-board PR run and a full run that lost 24\nboards are indistinguishable in JSON.\n\nFive tasks: put scope in the sidecar, render the markdown from the document, give the\ntwo early-exit paths a document, make _abandon_exit set a caveat field instead of\nprepending to a file it did not write, then pin the invariant that re-rendering the\nJSON reproduces the markdown byte for byte. Split out because it is a hil_test.py\nreporting refactor, and the abandon path runs while the interpreter is being torn\ndown - it deserves its own review.\n"
    },
    {
      "commit": "c514a6ee91a53927f55ba35e0a2933eddb5cefeb",
      "tree": "17e32eb998fe1e278d0226cb2d60c585136cd7a8",
      "parents": [
        "126e29f777f07a35b06b327b50e62e0150521b48"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 16:47:23 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 16:47:23 2026 +0700"
      },
      "message": "docs: spec, plan and outcome of the .claude instruction-surface audit\n\nNothing checked the agents, workflows and skills against the code they describe, and\nthe surface had drifted into stating incompatible rules. This records the protocol\nthat found the defects and what it cost.\n\nMethod: parallel subagents extract every falsifiable claim into JSONL ledgers; a\nvalidator re-reads each cited line and rejects any ledger whose quoted text is not\nthere, so an extractor that paraphrases or hallucinates fails a script rather than\nreaching the verification queue - 1,387 claims, zero such failures. Verification runs\nonly in the main session, and the same gate pointed at `citation` then checks the\nverifier\u0027s own work. Hard-earned evidence is source of truth: code is authoritative\nabout code, experience about hardware, so claims get a fourth verdict, EARNED, and\n\"no backing found\" is never grounds for deletion. All 1,387 claims carry a verdict;\nthe behavior sweep deliberately never emits CONFIRMED from a token match, because\nfinding a claim\u0027s vocabulary proves presence, not truth.\n\nEvery real defect came from cross-document comparison - none from any mechanical\npass. A path-existence lint was built, measured (11 flags on the audited tree, all\nfalse positives, and the target defect is lexically identical to correct text\nelsewhere), and rejected; recorded so nobody rebuilds it.\n"
    },
    {
      "commit": "126e29f777f07a35b06b327b50e62e0150521b48",
      "tree": "8aa0f665aef03c67a370d701147da19203d380bf",
      "parents": [
        "be86281b81b70adc1ce3746d620c912bf032fac2"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 16:47:22 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 16:47:22 2026 +0700"
      },
      "message": "skills, CLAUDE.md: correct instruction claims the source refutes\n\nFindings from an audit of the .claude instruction surface: 1,387 falsifiable claims\nextracted with a quote-gate (zero hallucinated), each verdicted against the code, the\nkernel at the rig\u0027s running version, or the rig itself. Only claims the current source\nactively refutes were touched; hard-earned rig knowledge stands as source of truth.\n\nusbtest told operators to stop the actions runner before touching hardware. Every\nother file forbids exactly that since the per-board flock landed - following it stops\nCI on a shared rig. Twice in the same file it said hil_test.py serializes usbtest\nbatteries; hil_lock.py budgets 2 concurrent per host controller, a profiled\nthroughput trade rather than a safety ceiling - while the recorded hazards stay: an\nunbudgeted battery has hard-frozen the rig through a VFIO xHCI PCIe error, and a\nmarginal DUT port bouncing under concurrent batteries has killed a uPD720201 outright,\nwhich lowering the widths does not fix. It also cited src/usb_descriptors.h and\nsrc/tusb_config.h as if repo-relative (they are the example\u0027s own, and the comment sat\nabove the cd that establishes the base) and presented usbtest_do_ioctl() and\ntools/usb/testusb.c as repo files when both are Linux kernel.\n\nusb-kernel-recover called the Renesas ppps \"real per-port\" in its rig layout while\nsaying four sections earlier that VBUS stays up. Both describe the same silicon and\nonly the second was right: owner-confirmed, the cards advertise ppps and do not\nimplement it, so a root cycle is purely a re-enumeration - both places now say so and\nwarn against reading uhubctl\u0027s flag as power control. The layout listed three cards;\nthe rig has five (01/03/04/05/06:00.0; AMD 02:00.0 has none), re-derived from\nlspci/uhubctl/sysfs and written as a derivation recipe because bus numbers renumber\nevery boot. The root-cycle rung also gains the board-flock requirement the other\nfiles already demand - it avoids the KERNEL device_lock, which is a different lock,\nand the text now names the two apart instead of reading as \"no reservation needed\".\n\nCLAUDE.md listed src/tusb_config.h among the key files; no such path exists -\ntusb_config.h is per-example, src/tusb_option.h is the file that lives there.\n"
    },
    {
      "commit": "be86281b81b70adc1ce3746d620c912bf032fac2",
      "tree": "e751548c35e8b3f09d55a5303e7e7d7acf3c6a6b",
      "parents": [
        "6905639b07c69fec68e9ebc77f7d27ac2775ee41"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 16:45:32 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 16:45:32 2026 +0700"
      },
      "message": "hil-pool-check: document the probe power-cycle escalation; name the env script directly\n\nA probe whose firmware has wedged reports flash-failed with the probe present and\n\"probe toggle unconfirmed\". The tool\u0027s own recovery cannot fix that: an authorized\ntoggle re-enumerates but never removes power, and hil_pool_check.py:304 already notes\nthat ST-Link, WCH-Link, CP210x and picoprobe keep their sysfs kobject across one. So\nthe check correctly gives up, and the operator was left to invent the next rung.\n\nWrite it down, as what this rig\u0027s hardware actually does rather than what uhubctl\nadvertises: the Renesas cards list their root hubs as ppps-capable but do not\nimplement it (owner-confirmed - VBUS never drops, only D+/D-), so a root-port cycle\nis a harder forced re-enumeration that a wedged probe can ride out, worth exactly one\nattempt; and the AMD 0000:02:00.0, where the WCH-Links live, has no port-power\nswitching at all - nothing to cycle, straight to a physical replug. Which card a\nprobe hangs off decides which case applies, so the procedure starts from readlink.\n\nThe ordering rules encode the shared-rig protocol: let the full run finish (a bounce\nre-enumerates siblings and corrupts checks still in flight), hold --all with this\nhost\u0027s --config before the cycle (hil_lock.py hold validates nothing against the\nroster and nothing maps a sysfs busport to a board name, so a narrower hand-listed\nhold reserves nothing while reporting success - and --all defaults to tinyusb.json,\nwhich on the tusb rig would reserve 27 boards that do not exist there), release\nBEFORE the re-check (hil_pool_check.py self-locks every board it checks, so a hold\nstill in place makes the verification report locked against your own hold and verify\nnothing), and drive the cycle through usb_recover.sh root-cycle by its full in-repo\npath - it is on no PATH and sudo\u0027s secure_path excludes the checkout. Never a bare\n`uhubctl -a cycle`: without -S it writes sysfs disable, whose disable_store takes the\nroot hub\u0027s lock uninterruptibly and then usb_disconnect()s the wedged child - the one\ninput that turns a probe wedge into a bus-wide wedge. Give the script the wedged\nprobe\u0027s own busport, not the hub path: the serial guard and the success check both\nread the path you pass, and the hub\u0027s inode always changes when its own port cycles.\n\nReporting asks for both passes: a final table showing every board healthy hides that\na probe needed power-cycling to get there, which is the signal that it will recur.\n\nAlso: the ESP-IDF env hints name `. $HOME/code/esp-idf/export.sh` instead of the\n`get-idf` alias, which lives only in interactive shells and fails from scripts.\n"
    },
    {
      "commit": "6905639b07c69fec68e9ebc77f7d27ac2775ee41",
      "tree": "2d46b0b6ae45b27df4874583c5830997df5829bd",
      "parents": [
        "7800876bf151a239046521232dc4603b156061be"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 16:43:49 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 20 16:43:49 2026 +0700"
      },
      "message": "hil: run every board in one hil_test.py and hand results across as JSON\n\nhil-validate ran one hil-operator per board. That parallelizes at the wrong layer:\nhil_test.py already schedules boards across host controllers and budgets concurrent\nflashes and usbtest batteries per controller (FLASH_PARALLEL/USBTEST_PARALLEL), and\nthose permits live in one process - N parallel runs multiply the budget onto the same\nuPD720201 cards for no wall-clock gain over one run that already parallelizes. The\nworkflow now spawns ONE operator with every board as repeated -b.\n\nThe operator no longer retypes the report table. Four consecutive max-effort review\nrounds found ~15 defects in this file and every one was in reconstructing board\nidentity from transcribed prose: report rows are named per VARIANT (nanoch32v203 only\never produces -fsdev/-usbfs rows), a variant need not start with its board\u0027s name,\nlock contention is a `board-locked` cell rather than a phrase, and each fix introduced\nthe next round\u0027s bug - including a fake-green test that asserted an invariant with the\none input shape that could not break it. The new helper test/hil/helper/hil_summary.py\ndoes the join where the roster lives and emits one machine verdict per board\n({board, ran, pass, locked, detail}); the operator returns that JSON verbatim plus\n`wedged`, the only field it authors, and the workflow reads fields, never parses a\nstring. Its cell classifier mirrors hil_test.py\u0027s own tally exactly: failures are\nalways marked (\u0027fail\u0027 or a ❌ prefix, TestFail\u0027s contract), everything unmarked is a\npass - a passing test may return a plain metric cell like \u002713443 KB/s\u0027, and the\nmirrored rule is what keeps a green table from becoming a red verdict.\n\nhil_ci.sh kept only the LAST -b, so multi-board remote runs staged one board\u0027s\nbinaries and every other board died on the rig after its lock and flash slot were\nspent. It now parses every -b spelling argparse accepts (with the -bt arms ordered\nfirst, longest-match, so the \u003cconfig\u003e.failed retry form is never read as a board named\n\"t...\"), pre-flights roster membership and build dirs for ALL boards before anything\nis wiped or staged, warns per declared variant with no build dir (which hil_test.py\nwould silently green-skip), forwards HIL_* knobs as export lines in one %q word the\nremote evals (\u0027; \u0027-joined so it round-trips under dash - an authorized\nHIL_NO_BOARD_LOCK force must not silently no-op), keeps HIL_REPORT_DIR local because\nthe copy-backs look in REMOTE_DIR, and copies hil_report.json and the .failed re-run\nspec back beside the markdown, deleting stale local copies first so a green run cannot\nleave last run\u0027s spec looking current.\n\nRetries preserve the fleet: the documented path is the \u003cconfig\u003e.failed spec, which\nalready begins with --accumulate; a fresh scoped re-run would unlink the report and\ncollapse the whole-fleet table to the retried boards alone.\n\nThe risky logic is executable, not argued about: .claude/workflows/test-hil-validate.mjs\npins the lookup/verdict helpers and runs in pre-commit (hil-validate-logic); nine\nstaging tests drive hil_ci.sh through an ssh stub that models the real thing (argv\njoined into one string the remote re-splits, heredoc on stdin - the naive echo-stub\npassed while the feature was broken); and deliberate mutations of the verdict logic\nare all caught.\n\nValidated on the rig: a 2-board run (usbtest 30/30 on both; the pre-fix classifier,\nreplayed against that run\u0027s real report, fails the fully-green stm32f723disco on its\ntwo passing \u002713443 KB/s\u0027 cells), the .failed --accumulate retry (merged report kept\nevery earlier row), and a 10-run soak over random subsets of a 22-board pool - 43\nboard-slots, every failure signature matched pre-existing CI state or known flake,\nzero tooling failures, no locks left behind.\n"
    },
    {
      "commit": "7800876bf151a239046521232dc4603b156061be",
      "tree": "31fd2349adcae0e7f8bdf4f10aae20765a3483f5",
      "parents": [
        "34e8d99ade2c18c4ab629f307544962e0af5347c",
        "75a01f561438c16677b48f3a59fda80a2b096ad8"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 19 18:33:12 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 19 18:33:12 2026 +0700"
      },
      "message": "Merge pull request #3833 from hathach/claude/ci-hs-set-address-order\n\ndcd(ci_hs): stage the device address before priming the status stage"
    },
    {
      "commit": "75a01f561438c16677b48f3a59fda80a2b096ad8",
      "tree": "9fada140524b9f6003ccd489240c3ae9598c7a21",
      "parents": [
        "9c202e8c657e001c8d16753aa90046a5f373575f"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 19 12:29:45 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 19 17:40:48 2026 +0700"
      },
      "message": "dcd(ci_hs): stage the device address before priming the status stage\n\nIMXRT1060RM 42.7.23 and UM10503 Table 478 both ask for the DEVICEADDR write\nwith USBADRA\u003d1 to happen after the SET_ADDRESS data phase and before the\nprime of the status stage, so the controller loads USBADR from its holding\nregister when the status stage is ACKed. The driver did it the other way\nround, leaving a window between the ENDPTPRIME store and the DEVICEADDR\nstore: an IN answered inside that window ACKs with USBADRA still 0, so the\nholding register is never consulted and the device keeps answering on\naddress 0 while the host has moved to the new one. Instruction timing alone\ncannot open that window, but dcd_set_address() runs in task context, so any\ninterrupt landing between the two stores stretches it past a microframe.\n\nHardware discards a staged address on a SETUP or OUT to endpoint 0 and\nzeroes USBADR on a bus reset, which covers a superseded SET_ADDRESS. What\nit cannot cover is a SETUP latched before this write and still unconsumed\nafter the full CI_HS_BUSY_SPIN spin, which refuses the prime: condition 2\nalready fired for that earlier SETUP, so the stage would survive and load\nUSBADR on the next EP0 IN ACK of an unrelated transfer. USB 2.0 9.4.6 is\nexplicit that \"the USB device does not change its device address until\nafter the Status stage of this request is completed successfully\", so the\nrefused-prime path restores the previous USBADR rather than leaving a stage\narmed. Restoring the previous value rather than writing zero keeps 9.4.6\u0027s\nAddress-state row correct, where a device already at a non-zero address\nmust stay there; on Linux that write is always a no-op, since hub_set_address\nonly issues SET_ADDRESS from USB_STATE_DEFAULT.\n\nCast dev_addr before the shift: it is uint8_t, promoted to int, so an\naddress of 64 or more reached the sign bit of a 32-bit int.\n\nNo errata applies: IMXRT1060CE_A Rev 1.3 lists only ERR050101 and ERR010661\nfor USB, IMXRT1060CE_B Rev 1.1 only ERR010661.\n\nValidated on mimxrt1064_evk: 18/19 device+host tests, 6x usbtest 30/30, and\na 100-iteration forced re-enumeration A/B that is clean on both this change\nand its parent (0/100 each). All 19 ci_hs boards build; unit tests 63/63;\nPVS drops one diagnostic (the sign-bit shift) and adds none.\n"
    },
    {
      "commit": "34e8d99ade2c18c4ab629f307544962e0af5347c",
      "tree": "99575dd56e43ed7d2cdcac57a43264c49986c231",
      "parents": [
        "82a577bde640c95ed25185af91c53b9f9f4ae29a",
        "0fab079ed8731d0b580a17a09a756d78e7a50a64"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 19 17:07:25 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 19 17:07:25 2026 +0700"
      },
      "message": "Merge pull request #3834 from hathach/claude/circleci-toolchain-cache-key\n\nci(circleci): key the toolchain cache off a file that always exists"
    },
    {
      "commit": "82a577bde640c95ed25185af91c53b9f9f4ae29a",
      "tree": "ed95fe5acc80c56cb48061711ec36a803082b95e",
      "parents": [
        "9c202e8c657e001c8d16753aa90046a5f373575f",
        "f065f280284a6f1b5b4c4d2849db4bff4a3ce70c"
      ],
      "author": {
        "name": "Zixun LI",
        "email": "admin@hifiphile.com",
        "time": "Wed Aug 19 11:30:29 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 19 11:30:29 2026 +0200"
      },
      "message": "Merge pull request #3816 from Ryzee119/ohci_gtd_fix\n\nohci: fix double allocation of dummy TDs in gtd_find_free"
    },
    {
      "commit": "0fab079ed8731d0b580a17a09a756d78e7a50a64",
      "tree": "26fcc01b540fab0a89ef4c8a430e6639706fa481",
      "parents": [
        "9c202e8c657e001c8d16753aa90046a5f373575f"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 19 15:41:13 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 19 15:41:13 2026 +0700"
      },
      "message": "ci(circleci): key the toolchain cache off a file that always exists\n\nRestore/Save Toolchain Cache have been failing on every job whose toolchain\nis hosted on GitHub - arm-gcc, arm-clang, riscv-gcc, rx-gcc, ft9xx-gcc:\n\n  Restore Toolchain Cache\n  template: cacheKey:1:8: executing \"cacheKey\" at \u003cchecksum \"toolchain_key\"\u003e:\n  error calling checksum: open /home/circleci/project/tinyusb/toolchain_key:\n  no such file or directory\n\n\"Set toolchain url and key\" only wrote toolchain_key when the URL was not a\ngithub.com link, but both cache steps referenced {{ checksum \"toolchain_key\" }}\nunconditionally, so for those toolchains the key could never be computed. The\njob still went green because the build step does not depend on the cache, which\nis why this went unnoticed - but the two steps are permanently red and the\ntoolchain is re-downloaded on every single run.\n\nKey the cache on the toolchain name plus a checksum of toolchain.json instead.\nThat file is in the repo, so the checksum always resolves, and the key still\ninvalidates whenever a toolchain URL changes. toolchain_key is no longer needed.\n\nSide effect worth calling out: GitHub-hosted toolchains are now cached rather\nthan skipped. That was the intent of the removed condition, but it is also what\nbroke the steps - CircleCI cannot skip a cache step on a value only known at\nrun time. Caching them also saves the repeated download.\n"
    },
    {
      "commit": "f065f280284a6f1b5b4c4d2849db4bff4a3ce70c",
      "tree": "7ca5519f9001b263dac05aab8b13228573f0c803",
      "parents": [
        "18bb2d650404432d0c6c61c26e98c7074f46ec6b"
      ],
      "author": {
        "name": "HiFiPHile",
        "email": "admin@hifiphile.com",
        "time": "Wed Aug 19 05:06:04 2026 +0200"
      },
      "committer": {
        "name": "HiFiPHile",
        "email": "admin@hifiphile.com",
        "time": "Wed Aug 19 05:06:04 2026 +0200"
      },
      "message": "ohci: defer descriptor reclaim until next frame\n"
    },
    {
      "commit": "9c202e8c657e001c8d16753aa90046a5f373575f",
      "tree": "0fbd5c7198067fcb13767486145393fdcbd7d07b",
      "parents": [
        "5c0e31cdabaf37f14e1f5e988a020abfc1000495",
        "8c3b880735f11c37828d6c8b700170e6b6e405ce"
      ],
      "author": {
        "name": "Zixun LI",
        "email": "admin@hifiphile.com",
        "time": "Wed Aug 19 05:00:20 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 19 05:00:20 2026 +0200"
      },
      "message": "Merge pull request #3830 from kasjer/kasjer/uac2-update-terminal-types\n\nUAC2: Add more terminal types"
    },
    {
      "commit": "8c3b880735f11c37828d6c8b700170e6b6e405ce",
      "tree": "6c1449fef76498cc5357f4528221a813171b0cf8",
      "parents": [
        "9b05c706272c5344284f7f8b4b53b1aefe082968"
      ],
      "author": {
        "name": "HiFiPHile",
        "email": "admin@hifiphile.com",
        "time": "Wed Aug 19 04:57:27 2026 +0200"
      },
      "committer": {
        "name": "HiFiPHile",
        "email": "admin@hifiphile.com",
        "time": "Wed Aug 19 04:57:27 2026 +0200"
      },
      "message": "Fix audio terminal type typos\n"
    },
    {
      "commit": "5c0e31cdabaf37f14e1f5e988a020abfc1000495",
      "tree": "1035bcaa6242d5bcbe5fd81827ecded0dafdcb6d",
      "parents": [
        "af7d199e73461c25555abbdc72441ab79eb74245",
        "19ff2ed615e4a97984aab5551ac8835ead53b9e7"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 22:59:49 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Aug 18 22:59:49 2026 +0700"
      },
      "message": "Merge pull request #3831 from hathach/fix-ci-hs\n\ndcd(ci_hs): rework bus reset handling per the reference manual, and work around ERR050101"
    },
    {
      "commit": "19ff2ed615e4a97984aab5551ac8835ead53b9e7",
      "tree": "1035bcaa6242d5bcbe5fd81827ecded0dafdcb6d",
      "parents": [
        "b925231216eabf277938607ba50f1f4b78c0ce7d"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Mon Aug 17 01:02:54 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 22:07:49 2026 +0700"
      },
      "message": "examples: document and work around the i.MX RT and LPC55 USB errata\n\nERR050101: while an isochronous IN endpoint is active, an IN token addressed to\nthat same endpoint number on ANOTHER device sharing the host can silently\nunprime one of this device\u0027s OUT endpoints - control, bulk, interrupt or\nisochronous alike. NXP states it cannot be detected by software and raises no\ninterrupt, so the endpoint simply stops answering and the transfer never\ncompletes.\n\nThe workaround is a uniqueness requirement rather than a particular number: the\nisochronous IN endpoint must not share its number with any IN endpoint in use on\nthe bus. One family-wide constant therefore defeats it, since two affected\nboards on the same hub then pick the same number and each becomes the other\u0027s\naggressor. CFG_TUSB_MIMXRT1XXX_ERRATA_ERR050101 is set only for the parts whose\nerrata list it - RT1015, RT1020, RT1024 and RT1050, where it is marked no fix\nscheduled, plus RT1060 and RT1064 rev A - so RT1010 and the RT11xx family keep\nthe ordinary number and cannot collide with an affected board beside them.\nSeveral affected boards on one hub can still be given distinct numbers with\n-DEPNUM_ISO_IN.\n\nThe guard covers every example that has an isochronous IN endpoint: audio_test,\naudio_4_channel_mic, uac2_headset, cdc_uac2, usbtest, video_capture and\nvideo_capture_2ch. The video examples move the endpoint only when streaming\nisochronously, since the bulk configuration is unaffected, and video_capture_2ch\ntakes two numbers because it has two streams.\n\nThe macro name follows CFG_TUSB_RP2_ERRATA_E2/E4/E15 already in tree, and its\nis fixed, and which cannot be told apart at compile time - a way to define it to\n0.\n\ndevice_issues.rst records ERR050101 against every affected part with a link to\neach errata sheet, and adds the LPC55S2x USB.3 speed-detection and USB.5\nisochronous IN entries, neither of which TinyUSB works around. The branch\u0027s\ndesign notes are included under docs/superpowers.\n\nVerified: 340 wedge-free runs on mimxrt1064_evk, which previously wedged within\nhours, and the macro resolving to endpoint 0x87 on mimxrt1064_evk against 0x83\non mimxrt1010_evk and stm32f407disco.\n"
    },
    {
      "commit": "b925231216eabf277938607ba50f1f4b78c0ce7d",
      "tree": "6c1e44d1d196c374b74572f0dd40fae909e68611",
      "parents": [
        "af5354349156d3d1bb0f2533ad802f1e1c5a6ffb"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Mon Aug 17 01:02:39 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 22:07:49 2026 +0700"
      },
      "message": "bsp(lpc55): run lpcxpresso55s28 as a high-speed device, add it to the ci pool\n\nFlip the board to device-highspeed/host-fullspeed, matching lpcxpresso55s69\nand the way it is cabled on the test rig, and add it to the rig pool with\nthe unique id read from its flash PFR. This is the first hardware coverage\nthe ip3511 high-speed device path has ever had, and it immediately exposed\nthe clear-stall type-bit bug fixed separately.\n\nThe port swap also exposed a build gap: family.mk only linked a host\ncontroller for port 1, so make host builds on port 0 failed with undefined\nreferences - mirror family.cmake and link the OHCI driver there. The board\u0027s\nrhport defaults now come from family.cmake\u0027s guarded ones rather than a\nduplicate copy, so a -D override on the command line wins.\n"
    },
    {
      "commit": "af5354349156d3d1bb0f2533ad802f1e1c5a6ffb",
      "tree": "3e78e835ac6ca75c793c3078aeca48d9753ebce3",
      "parents": [
        "5baf5925c8b6a033de85e3b5537ea879de75e3da"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Mon Aug 17 01:02:38 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 22:07:49 2026 +0700"
      },
      "message": "bsp(lpc11u37): move the main stack to the USB SRAM bank\n\nThe 8 KB main bank is packed tightly enough that only ~280 bytes remained above\n.bss, and interrupt frames overflowed into the topmost task stack - a hard fault\nin cdc_msc_freertos. Put the MSP at the top of the 2 KB USB SRAM bank, which\nnothing else uses in either build system, so the stack no longer shrinks as .bss\ngrows. The Make build\u0027s CFG_TUSB_MEM_SECTION placement of endpoint buffers into\nthat bank is dropped so both build systems agree on the layout.\n\nThe headroom assert is written as an addition rather than a subtraction, since\nlinker script arithmetic is unsigned and an overflowing bank would underflow the\ndifference into a huge positive value and pass silently.\n"
    },
    {
      "commit": "5baf5925c8b6a033de85e3b5537ea879de75e3da",
      "tree": "afadd39899510c9982b26ae5b396d04e7946823a",
      "parents": [
        "a85a6afc6d98726f5edfb2d7606527c87c963dba"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Mon Aug 17 01:02:22 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 22:07:49 2026 +0700"
      },
      "message": "dcd(ip3511): fix DEVCMDSTAT write-1-to-clear handling and EP0 setup races\n\nDEVCMDSTAT mixes read/write fields with write-1-to-clear latches, so a blind\nread-modify-write writes a pending latch back as a one and silently clears it -\na setup consumed that way strands EP0. Mask the latches on every update.\n\nThe setup path follows the manual\u0027s order: acknowledge the latch, then read the\npayload. The EP0 IN interrupt is cleared along with EP0 OUT, as the control\nendpoint flowchart requires - a control IN completion latched before the setup\nmust not reach usbd after it, where it would be applied to the request the setup\njust started and arm its status stage early.\n\nThe payload is copied a byte at a time out of a buffer now declared volatile:\nthe controller DMAs a new setup packet into it as soon as the latch is cleared,\nand C orders volatile accesses only against each other, so gcc sinks a plain\nmemcpy below the guard read that follows at -O2 and -O3 - leaving only -Os, the\nlevel CI builds, correct.\n"
    },
    {
      "commit": "a85a6afc6d98726f5edfb2d7606527c87c963dba",
      "tree": "9dfea7e09f90b56e7fc9445edeb05299fa75cf8e",
      "parents": [
        "2fda873fa5f6ef0c893f4f138b5c54e49c24e0a9"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Mon Aug 17 01:02:07 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 22:07:49 2026 +0700"
      },
      "message": "usbd: handle a refused transfer without halting, and report it\n\nA refused transfer is a recoverable condition - a new setup superseding a\ncontrol response, for instance - rather than a bug, but every failure path\ntreated it as one. TU_ASSERT carries TU_BREAKPOINT, which is gated on a debugger\nbeing attached rather than on CFG_TUSB_DEBUG, so on a rig where a probe is\nalways attached it halted the CPU even in release builds. Use TU_VERIFY on the\ncontrol transfer paths, including the multi-packet data stage continuation, and\ndrop the breakpoint from the endpoint transfer failure arm, which already marks\nthe endpoint ready again so the next transfer can proceed.\n\nThe result of usbd_control_xfer_cb() was separately dropped on the floor,\nleaving EP0 neither armed nor stalled and nothing recorded. It is logged now,\nand deliberately not stalled: a DCD refuses an EP0 prime when a newer setup is\nalready latched, and EP0 stalls are cleared by hardware when that setup arrives,\nso a stall issued here would land after the auto-clear and stall the transfer\nthat superseded this one. The pending setup re-drives EP0 by itself.\n"
    },
    {
      "commit": "2fda873fa5f6ef0c893f4f138b5c54e49c24e0a9",
      "tree": "57dd6c3639ba3d046b7cdf9289a4eb1b50cc271c",
      "parents": [
        "073942589355676980ba401cb88c0eb9f065e468"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Mon Aug 17 01:01:54 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 22:07:49 2026 +0700"
      },
      "message": "dcd(ci_hs): rework bus reset handling and bound the register waits\n\nA bus reset was detected only from the port change that ends it, which is late:\nthe manual asks the DCD to clear the endpoint semaphores, cancel every prime and\nfree the dTDs while the reset is still being driven. Enable the reset interrupt\nand do all of that there, in the manual\u0027s order (IMXRT1060RM 42.5.6.2.1,\np.2394), including the two steps that were missing - confirming the port is\nstill being reset, and freeing the dTDs. A failed check means the cleanup\narrived late and the controller may be in an undefined state, so the manual\u0027s\nremedy is carried out rather than noted: a controller reset, followed by the\nfull re-initialisation it then requires, since the reset detaches the device.\n\nThe port change that ends the reset is left with what the manual gives it, the\nnegotiated speed, which the new BUS_RESET_END event carries. A port change is\nclassified by the interrupt that preceded it: a suspend raises no port change of\nits own, the resume that ends it does.\n\nEvery unbounded register spin is now bounded. They waited on bits the hardware\nclears within a frame, but each could hang an interrupt handler outright on a\ncontroller that had stopped responding. The endpoint flush follows all three\nsteps of IMXRT1060RM 42.5.6.6.5 (p.2413), repeating a flush the controller\nrefuses while a packet is in progress - previously reported as success.\n\nEP0 setup handling is hardened alongside: the payload is copied out of the queue\nhead through the volatile qualifier before ENDPTSETUPSTAT is cleared, since that\nclear releases the setup lockout and a back-to-back setup can overwrite the\nbuffer immediately after, and C orders volatile accesses only against each\nother, so a plain memcpy may legally be sunk past the store.\n\nThere is deliberately no unplug detection. IMXRT1060RM 42.7.31 (p.2470) states a\nzero Current Connect Status means the device \"did not attach successfully or was\nforcibly disconnected by the software writing a zero to the Run bit ... It does\nnot state the device being disconnected or suspended\", so a cable pull raises no\nport change at all; VBUS via OTGSC is the manual\u0027s disconnect indicator and is\nboard dependent.\n\nVerified on mimxrt1064_evk: 30 forced bus resets each re-enumerating at high\nspeed with no descriptor errors, plus repeated full usbtest batteries at 30/30\nacross the series.\n"
    },
    {
      "commit": "073942589355676980ba401cb88c0eb9f065e468",
      "tree": "a6447b4ee77f12a8127ea45d78fbfdc09c9e0337",
      "parents": [
        "af7d199e73461c25555abbdc72441ab79eb74245"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Mon Aug 17 01:01:37 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 20:51:21 2026 +0700"
      },
      "message": "usbd: split bus reset into start/end edge events\n\nA driver that can see reset signalling begin has no way to say so: the only\nevent carries the negotiated speed, which does not exist until the reset\nends. On ChipIdea that left the stack believing it was still configured for\nthe whole reset window - 3 ms at minimum, tens of milliseconds in practice -\nwhile the controller had already torn its endpoints down, so a class driver\nwriting in that window primed a disabled endpoint over a zeroed queue head.\n\nAdd DCD_EVENT_BUS_RESET_START for the leading edge and rename the existing\nevent to DCD_EVENT_BUS_RESET_END, keeping DCD_EVENT_BUS_RESET as an alias.\nSTART is optional and END stays self-sufficient, so every other driver and\nthe unit tests are untouched.\n"
    },
    {
      "commit": "9b05c706272c5344284f7f8b4b53b1aefe082968",
      "tree": "9aafc1332b4382ac67eef66f87068ad54e21fe93",
      "parents": [
        "af7d199e73461c25555abbdc72441ab79eb74245"
      ],
      "author": {
        "name": "Jerzy Kasenberg",
        "email": "jerzy.kasenberg@codecoup.pl",
        "time": "Tue Aug 18 15:37:18 2026 +0200"
      },
      "committer": {
        "name": "Jerzy Kasenberg",
        "email": "jerzy.kasenberg@codecoup.pl",
        "time": "Tue Aug 18 15:37:37 2026 +0200"
      },
      "message": "UAC2: Add more terminal types\n\nThis adds more standard terminal types that can\nbe used in descriptors to improve end user experience\nwhen operating system can present more acurate\nimage for audio device terminal.\n\nSigned-off-by: Jerzy Kasenberg \u003cjerzy.kasenberg@codecoup.pl\u003e\n"
    },
    {
      "commit": "af7d199e73461c25555abbdc72441ab79eb74245",
      "tree": "4090122073c645ab5c73cf58cb01075950a2f88f",
      "parents": [
        "11bdbc3eac085e4a946e25a8a614bcbf62e41fa2",
        "f59c8948729debc6d57c4dfade5486176468edbf"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 18:07:05 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Aug 18 18:07:05 2026 +0700"
      },
      "message": "Merge pull request #3829 from hathach/claude/read-doc-calibre-db\n\nread-doc skill: search the document library\u0027s database instead of the filesystem"
    },
    {
      "commit": "f59c8948729debc6d57c4dfade5486176468edbf",
      "tree": "4090122073c645ab5c73cf58cb01075950a2f88f",
      "parents": [
        "11bdbc3eac085e4a946e25a8a614bcbf62e41fa2"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 15:28:58 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 16:58:58 2026 +0700"
      },
      "message": "skill(read-doc): search the Calibre database instead of the filesystem\n\nFinding documents by walking the library tree misses anything the filename\ndoes not carry - Calibre stores only a truncated title and the author there, so\nthe tags, series, publisher and description that hold most part numbers and\nerrata IDs are invisible to it. A zero-result tree search then reads as \"the\ndocument does not exist\" rather than as a bad search; that happened here, and\nled to a confident claim that a fully populated 14,000-file library was empty.\n\nsearch.py queries metadata.db, ANDs its keywords across every metadata field\n(including the stored filename), and prints the best matches first with the\nexact path to read. Matching is NFKC + casefold, so a typed ASCII apostrophe or\nmu reaches the titles that store the typographic ones.\n\nEvery printed path is checked on disk. Calibre renames \u003cauthor\u003e/\u003ctitle\u003e (\u003cid\u003e)\nwhen metadata is edited and leaves the old directory behind, so a miss retries\nby the stable book id before reporting MISSING - which distinguishes \"the file\nis not here right now\" from \"no such document\". The gate tests for metadata.db\nrather than the directory, since an unmounted or half-synced mountpoint is\nstill a directory.\n\nConsumers that prescribed their own tree search - driver-reviewer, port-dev,\nthe driver-review workflow, and the calibre-library references in CLAUDE.md,\nusbtest, etm-trace and target-debug - now point at the skill, which owns the\nlibrary\u0027s location.\n"
    },
    {
      "commit": "11bdbc3eac085e4a946e25a8a614bcbf62e41fa2",
      "tree": "c592e9bfb7e097ca0cee9a47673bf6dd42545845",
      "parents": [
        "2465ea8f435114af3b3c935cc4fbed423d9eac69",
        "c7290c4d3167766055f492de43f1ede83940e23c"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 14:33:01 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Aug 18 14:33:01 2026 +0700"
      },
      "message": "Merge pull request #3803 from hathach/claude/hil-wedge-containment\n\nhil, ci: contain a wedged USB stack instead of stranding the runner"
    },
    {
      "commit": "c7290c4d3167766055f492de43f1ede83940e23c",
      "tree": "c592e9bfb7e097ca0cee9a47673bf6dd42545845",
      "parents": [
        "f822f69a9871b2115c5213889d70411da66ca1b1"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Mon Aug 17 19:20:32 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 12:19:09 2026 +0700"
      },
      "message": "docs: hand off follow-up work as per-PR plans\n\nRecords the convention in CLAUDE.md -- deferred work is a SEPARATE scope that deserves\nits own PR, written by another session, so it is handed off as a writing-plans doc in\ndocs/superpowers/followup/pr\u003cNNN\u003e-\u003ctopic\u003e.md rather than accumulated in the PR that\nfound it.\n\nFive handoffs from #3803: flasher_recover (convoy-safe recovery for J-Link boards, seven\nvalidated on the rig), the blindness reporting gaps, the usbtest recovery reserve, the\nIAR re-run spec, and the pci-rebind stranding question. Each carries what is already\nestablished with its citations and measurements, what remains, and why it was split out.\n\nOne doc per follow-up, not one per PR: a per-PR file invites unrelated work into the\nsame document and rots as a unit.\n"
    },
    {
      "commit": "f822f69a9871b2115c5213889d70411da66ca1b1",
      "tree": "e8d1ddba4dbed71112af7f216aa2e677321a8c1c",
      "parents": [
        "3963a1b70a572132aced1c1a0033e1c8249a0c7e"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 14 01:08:50 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 12:19:09 2026 +0700"
      },
      "message": "skills, docs: rewrite USB recovery from the live incidents\n\nTwo things the rig taught us that the old guidance got wrong.\n\nA usbfs ioctl wedged in D state cannot be freed on a running kernel. It holds\nthe device lock, so usb_disconnect() blocks behind it; reboot(2) walks\ndevice_shutdown() and takes the same lock, so every userspace reboot stalls too.\nOnly sysrq b (emergency_restart, which skips device_shutdown) or hypervisor\naction clears it -- all cited to the kernel source. The recovery ladder is\ngeneric across rigs now (ci.lan, hifiphile, a bench PC) and ends at hypervisor\nescalation only where host access exists. Two claims are corrected outright:\nJLinkExe is NOT convoy-safe, and a park-flash cannot free a device-lock owner.\n\nThe hil skill\u0027s banner list is what an operator agent matches a report against,\nso it enumerates the banners that actually exist, including the D-state note --\nwhich is explicitly NOT a wedge, since a healthy in-flight testusb is\nuninterruptible for most of every case and a concurrent CI battery would\notherwise turn a clean run red.\n"
    },
    {
      "commit": "3963a1b70a572132aced1c1a0033e1c8249a0c7e",
      "tree": "f612deb9c6dc878984ba59c72c74f04741925187",
      "parents": [
        "2465ea8f435114af3b3c935cc4fbed423d9eac69"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Fri Aug 14 01:08:40 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Tue Aug 18 12:19:09 2026 +0700"
      },
      "message": "test/hil, ci: contain a wedged USB stack instead of stranding the runner\n\nA wedged USB device used to take the whole HIL run with it. Every worker that\ntouched the poisoned node blocked uninterruptibly, the pool could not be joined,\nmap_async discarded every board\u0027s result, and the job ran to the GitHub ceiling\nwith no report at all -- while the self-hosted runner\u0027s single job slot stayed\noccupied and every queued job waited behind it.\n\nBound the calls a worker makes itself. read_sysfs, bounded_open and run_cmd all\nanswer within a wall clock; read_sysfs distinguishes \"absent\" from \"unknown\",\nbecause a blocked read is not evidence of absence, and caps stranded readers at\nfour (each costs a thread and an fd for the life of the process) after which the\nworker declares itself blind. mtype, the gio unmount, the libmtp session and the\narecord/iperf reaps go through those bounds; the MTP session runs in a disposable\nsubprocess, since libmtp\u0027s ctypes calls block unkillably in D state.\n\nBound the run. A pool guard (HIL_POOL_TIMEOUT, 60 min) fires before any job\nceiling and still writes a report. When the pool will not shut down, the sweep\nkills what the workers spawned -- descendants, not just direct children, since\nflashers run in their own session -- confirms each kill actually landed, and\nexits early so the runner is freed. Whatever survived is named in the report.\n\nDeliberately shallow past that point. We do not re-scan process groups, prove\npid ownership, or escalate through sudo: a root-owned survivor is reported, not\nforce-killed, because signalling a pid we cannot prove is ours is the worse\nfailure, and the job ceiling backstops whatever this misses. A D-state holder\nwas never killable anyway.\n\nRecover instead of reporting a wedge. A HUNG usbtest case reflashes its own DUT\nthrough its roster flasher, but only where the flasher can reach its probe past\na poisoned node -- openocd pinned to a validated vid_pid, or esptool. Where it\ncannot, the run says so rather than reserving budget for a path that cannot fire.\n\nRaise the CI ceilings above the pool guard so the guard fires first and still\nwrites its report, and pin --retry 1 on every HIL leg: the guard is a flat\nconstant and does not scale with max_retry, so argparse\u0027s default of 3 would\ntriple the serialized usbtest tail against an unchanged guard.\n\nSplit the module: execution in hil_test/hil_flash/usbtest, infrastructure in\nhelper/ (locking, health, selection, shared bounded IO), and the two matrix\ngenerators into .github/scripts/ -- ci_set_matrix.py sat in workflows/, where\nGitHub treats every file as a workflow definition. 193 tests cover the bounded\npaths, the kill ladder, the guard and the selector against synthetic /proc trees\nand PATH-injected fakes; a real wedge cannot be manufactured on demand.\n"
    },
    {
      "commit": "2465ea8f435114af3b3c935cc4fbed423d9eac69",
      "tree": "a47ef0f4ecb66ecba5d00e286fc4bf6f067096a9",
      "parents": [
        "9fb2f9cb3f8e30fa9ca2a32f4cdc6f0fb07a5be3",
        "8ccd0d549798c66d484e5a4b4c57edf49e8bb097"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Mon Aug 17 19:04:37 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Aug 17 19:04:37 2026 +0700"
      },
      "message": "Merge pull request #3790 from hathach/fix/lpc43-hfp-reliability\n\nFix HFP HIL reliability issue"
    },
    {
      "commit": "18bb2d650404432d0c6c61c26e98c7074f46ec6b",
      "tree": "c14d022f455098310cf57c62519e3e32a3cfe481",
      "parents": [
        "73e787ae418df76b79ac0fd31bb0674a72e68c88"
      ],
      "author": {
        "name": "Ryzee119",
        "email": "wendland@live.com.au",
        "time": "Wed Aug 12 21:49:00 2026 +0930"
      },
      "committer": {
        "name": "Ryzee119",
        "email": "wendland@live.com.au",
        "time": "Mon Aug 17 18:59:52 2026 +0930"
      },
      "message": "ohci: reclaim orphaned TDs on device disconnect\n"
    },
    {
      "commit": "8ccd0d549798c66d484e5a4b4c57edf49e8bb097",
      "tree": "d048ac65594bea74f658457383e0aa4faaaa58f0",
      "parents": [
        "6271842ea874812f1157b42bcd650c59ab59259b"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 13 14:35:01 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Mon Aug 17 12:12:08 2026 +0700"
      },
      "message": "portable/chipidea: name SBUSCFG in ci_hs_regs_t, unify AHB burst hook\n\nReplace the duplicated per-MCU dispatch in dcd_init/hcd_init and the two\nhelper flavors (USB_Type access on iMX RT, raw offset 0x90 on LPC18/43)\nwith one SBUSCFG register field plus a per-header CI_HS_SET_AHB_BURST()\nhook, compiled only where defined. The LPC USB0-only policy is now\nvisible at the macro definition.\n"
    },
    {
      "commit": "9fb2f9cb3f8e30fa9ca2a32f4cdc6f0fb07a5be3",
      "tree": "f6c24f479c9846622fadaa481a02b6064edb7084",
      "parents": [
        "801be6c97f972b730b915708e7c4505cf7c83454",
        "dfd197ff0c83a01ac55a99b85f2e8f3794ea0a47"
      ],
      "author": {
        "name": "Zixun LI",
        "email": "admin@hifiphile.com",
        "time": "Sat Aug 15 14:28:57 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Aug 15 14:28:57 2026 +0200"
      },
      "message": "Merge pull request #3823 from sauloverissimo/feat/midi2-fb-caps\n\nmidi2 device: complete the UMP stream discovery responder"
    },
    {
      "commit": "801be6c97f972b730b915708e7c4505cf7c83454",
      "tree": "b41f5d4eb5c311747b7972cd82d72440db96cc62",
      "parents": [
        "09b02650e5cbed2b38a153ae8a72a4c104bb4985",
        "8737c5adfca7e51003e743bcc8bcefed837fb1d8"
      ],
      "author": {
        "name": "Zixun LI",
        "email": "admin@hifiphile.com",
        "time": "Sat Aug 15 06:13:52 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Aug 15 06:13:52 2026 +0200"
      },
      "message": "Merge pull request #3802 from dxbjavid/video-payload-cap\n\nclamp committed video payload size to streaming ep buffer"
    },
    {
      "commit": "8737c5adfca7e51003e743bcc8bcefed837fb1d8",
      "tree": "a9004f37ce902596f6adedb25bcdf5fa2a90f849",
      "parents": [
        "15dd3120ac4a9dea0d979dc541ef8e0f52f5aa26"
      ],
      "author": {
        "name": "Zixun LI",
        "email": "admin@hifiphile.com",
        "time": "Sat Aug 15 05:51:43 2026 +0200"
      },
      "committer": {
        "name": "HiFiPhile",
        "email": "admin@hifiphile.com",
        "time": "Sat Aug 15 05:56:10 2026 +0200"
      },
      "message": "Potential fix for pull request finding\n\nCo-authored-by: Copilot Autofix powered by AI \u003c175728472+Copilot@users.noreply.github.com\u003e\nSigned-off-by: HiFiPhile \u003cadmin@hifiphile.com\u003e\n"
    },
    {
      "commit": "09b02650e5cbed2b38a153ae8a72a4c104bb4985",
      "tree": "c80bd661f33d9daa1e43c2699ddaca3bf1c99b34",
      "parents": [
        "53fef28335181fd6b4c8fb2ac65fdacf44659492",
        "af81f9ef42254301c2239eed657b0adce8b466b0"
      ],
      "author": {
        "name": "Zixun LI",
        "email": "admin@hifiphile.com",
        "time": "Sat Aug 15 05:43:53 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Aug 15 05:43:53 2026 +0200"
      },
      "message": "Merge pull request #3822 from ice458/fix/usbtmc-trigger-bulk-out-rearm\n\nusbtmc: re-arm (or stall) the bulk-OUT endpoint after a USB488 TRIGGER message"
    },
    {
      "commit": "dfd197ff0c83a01ac55a99b85f2e8f3794ea0a47",
      "tree": "4a205587fcb665e931aac7a478c5f05804174f98",
      "parents": [
        "0504faf29825deb130bfeb88dba46bb1c1bdec75"
      ],
      "author": {
        "name": "HiFiPhile",
        "email": "admin@hifiphile.com",
        "time": "Sat Aug 15 05:11:55 2026 +0200"
      },
      "committer": {
        "name": "HiFiPhile",
        "email": "admin@hifiphile.com",
        "time": "Sat Aug 15 05:11:55 2026 +0200"
      },
      "message": "fix(midi2): fix discovery response racing\n\nSigned-off-by: HiFiPhile \u003cadmin@hifiphile.com\u003e\n"
    },
    {
      "commit": "0504faf29825deb130bfeb88dba46bb1c1bdec75",
      "tree": "814a2d5a846f6fa5ff3849a83443d8dcdb42ef55",
      "parents": [
        "3c9e92c60abf3959ef0367f5965b53b2def28d58"
      ],
      "author": {
        "name": "Saulo Veríssimo",
        "email": "sauloverissimo@gmail.com",
        "time": "Fri Aug 14 16:43:36 2026 -0300"
      },
      "committer": {
        "name": "Saulo Veríssimo",
        "email": "sauloverissimo@gmail.com",
        "time": "Fri Aug 14 16:43:36 2026 -0300"
      },
      "message": "fix(midi2): keep discovery replies valid under TX pressure\n\nText replies resume instead of dropping their tail packets, which used\nto leave a Start/Continue sequence without an End. A new Function Block\nDiscovery now merges with a pending one instead of replacing it.\n"
    },
    {
      "commit": "3c9e92c60abf3959ef0367f5965b53b2def28d58",
      "tree": "a84465abf1773576f848be386e1a26f3644d54e7",
      "parents": [
        "16629759cd26973cd8e26bee632b339e718f83ba"
      ],
      "author": {
        "name": "Saulo Veríssimo",
        "email": "sauloverissimo@gmail.com",
        "time": "Fri Aug 14 15:21:24 2026 -0300"
      },
      "committer": {
        "name": "Saulo Veríssimo",
        "email": "sauloverissimo@gmail.com",
        "time": "Fri Aug 14 15:21:24 2026 -0300"
      },
      "message": "example(midi2): report device identity in midi2_device\n"
    },
    {
      "commit": "16629759cd26973cd8e26bee632b339e718f83ba",
      "tree": "568bdbbfb69c582a8198c0a6d593d6401eb2ec22",
      "parents": [
        "53fef28335181fd6b4c8fb2ac65fdacf44659492"
      ],
      "author": {
        "name": "Saulo Veríssimo",
        "email": "sauloverissimo@gmail.com",
        "time": "Fri Aug 14 15:21:18 2026 -0300"
      },
      "committer": {
        "name": "Saulo Veríssimo",
        "email": "sauloverissimo@gmail.com",
        "time": "Fri Aug 14 15:21:18 2026 -0300"
      },
      "message": "feat(midi2): complete the UMP stream discovery responder\n\nAdds the Device Identity Notification with an app callback, MIDI-CI\nversion and SysEx8 stream count in FB Info, honors the Endpoint\nDiscovery filter bitmap, and paces discovery replies by TX FIFO room.\n"
    },
    {
      "commit": "af81f9ef42254301c2239eed657b0adce8b466b0",
      "tree": "c80bd661f33d9daa1e43c2699ddaca3bf1c99b34",
      "parents": [
        "282d46e68d9100af0dfdcc01e7689bb63bbf8419"
      ],
      "author": {
        "name": "ice458",
        "email": "85405449+ice458@users.noreply.github.com",
        "time": "Fri Aug 14 16:23:38 2026 +0900"
      },
      "committer": {
        "name": "ice458",
        "email": "85405449+ice458@users.noreply.github.com",
        "time": "Fri Aug 14 16:23:38 2026 +0900"
      },
      "message": "usbtmc: document why the trigger re-arm result is ignored\n\nA false return from tud_usbtmc_start_bus_read() here does not mean arming\nfailed: it means the endpoint is already armed, either because the\napplication re-armed it from its trigger callback or because a transfer is\nstill queued (usbd_edpt_xfer() reports failure when the endpoint is busy).\nBoth cases end in STATE_IDLE, so the state cannot disambiguate them either,\nand stalling on the result would halt a healthy endpoint.\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n"
    },
    {
      "commit": "282d46e68d9100af0dfdcc01e7689bb63bbf8419",
      "tree": "d81e3b5b1a332e16de5375f3a18269ce2f920d74",
      "parents": [
        "53fef28335181fd6b4c8fb2ac65fdacf44659492"
      ],
      "author": {
        "name": "ice458",
        "email": "85405449+ice458@users.noreply.github.com",
        "time": "Fri Aug 14 16:00:06 2026 +0900"
      },
      "committer": {
        "name": "ice458",
        "email": "85405449+ice458@users.noreply.github.com",
        "time": "Fri Aug 14 16:00:06 2026 +0900"
      },
      "message": "usbtmc: re-arm (or stall) the bulk-OUT endpoint after a USB488 TRIGGER\n\nA single USB488 TRIGGER message left the bulk-OUT endpoint un-armed, so the\nhost\u0027s next bulk-OUT transfer timed out. The trigger itself succeeded\nsilently, so the failure surfaced on a later, unrelated command; only a\nUSBTMC device clear recovered it. The bundled examples/device/usbtmc\nreproduced this as shipped.\n\nEvery other branch of the STATE_IDLE dispatch in usbtmcd_xfer_cb() leaves\nthe endpoint in a defined state: it either transitions out of STATE_IDLE so\na later tud_usbtmc_start_bus_read() can re-arm it, or it stalls and lets the\nCLEAR_FEATURE(ENDPOINT_HALT) handler recover it. USBTMC_MSGID_USB488_TRIGGER\ndid neither, and because the state stayed STATE_IDLE, even an application\nfollowing the contract documented in usbtmc_device.h got a silent no-op from\ntud_usbtmc_start_bus_read().\n\nTransition to STATE_NAK so the re-arm can take effect, and stall the\nendpoint when trigger is unsupported or the application callback rejects it,\nmatching the existing handling for messages the driver cannot process. The\ncallback result is deliberately not wrapped in TU_VERIFY(), which would\nreturn before the stall/re-arm and reintroduce the same hang.\n\nSince the driver now re-arms after a trigger, drop tud_usbtmc_msg_trigger_cb\nfrom the list of callbacks after which the application must do so.\n\nFixes #3821\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n"
    },
    {
      "commit": "53fef28335181fd6b4c8fb2ac65fdacf44659492",
      "tree": "acb6dead21113cc3b5d909dad9a5e5e93118fc35",
      "parents": [
        "dc3927fd293126d6f2b835153155ba7830f5707f",
        "91fbbd192ca9539221d3dc096f00ce77836a5d3d"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 13 16:57:33 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Aug 13 16:57:33 2026 +0700"
      },
      "message": "Merge pull request #3817 from hathach/claude/usbd-setup-queue-leak\n\nusbd: fix queued-setup counter leak when the event queue is full"
    },
    {
      "commit": "91fbbd192ca9539221d3dc096f00ce77836a5d3d",
      "tree": "801ad543cc2608eb5804c3d3d0fd547246d08d83",
      "parents": [
        "a52562b2be7ea728a176ae94d8a18d9ae0a4423a"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 12 23:05:59 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 13 11:45:36 2026 +0700"
      },
      "message": "usbd: clear endpoint busy/claimed when a completion event is dropped\n\nAn XFER_COMPLETE dropped by a full event queue leaves its endpoint\u0027s\nBUSY|CLAIMED state set forever - the consumer that normally clears it\nnever sees the event, so usbd_edpt_claim()/usbd_edpt_xfer() fail from\nthen on and the class never re-arms the endpoint. Clear both flags when\nthe enqueue fails: the completion is lost either way, but the endpoint\nstays usable.\n\nUnit test: arm a bulk endpoint, drop its completion against a full\nqueue, verify the endpoint can be claimed and re-armed.\n"
    },
    {
      "commit": "dc3927fd293126d6f2b835153155ba7830f5707f",
      "tree": "f25da76dc993f4ad5a4b388a83691c345d31457b",
      "parents": [
        "32530d8f4b6cd6d6f7a7df7e6358856c7ebc4f5e",
        "853cbff468cde5821447ecc92063acf11d3e696e"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Thu Aug 13 09:15:52 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Aug 13 09:15:52 2026 +0700"
      },
      "message": "Merge pull request #3818 from hathach/claude/lpc55-board-uid\n\nhw/bsp/lpc55: implement board_get_unique_id from flash PFR UUID"
    },
    {
      "commit": "853cbff468cde5821447ecc92063acf11d3e696e",
      "tree": "8a2bb3d590b78f8fa1d8a2b1c390b19f480900d6",
      "parents": [
        "f3021b337fcea154b898489c417d428c92f88e92"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 12 23:04:33 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 12 23:04:33 2026 +0700"
      },
      "message": "hw/bsp/lpc55: implement board_get_unique_id from flash PFR UUID\n\nRead the 128-bit device UUID from the flash PFR region at 0x0009FC70\n(UM11126 rev 2.1, section 48.8) rather than falling back to the fixed\nweak default in hw/bsp/board.c.\n\nVerified on lpcxpresso55s69: cdc_msc enumerates with SerialNumber\nE059C3E208F9B955B3BA4C5CC7F3D13D, matching the uid already recorded for\nthat board in test/hil/local.json.\n"
    },
    {
      "commit": "a52562b2be7ea728a176ae94d8a18d9ae0a4423a",
      "tree": "a9bf72e78870c92deee6896e276c4b9c04b30cb5",
      "parents": [
        "a0249ada9096365697340031a7b4a285beb18a2b"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 12 22:37:06 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 12 22:41:17 2026 +0700"
      },
      "message": "usbd: clear the queued-setup counter on bus reset\n\nA SETUP counted before a bus reset must not be carried across it: the\nconsumer would either skip a post-reset SETUP (count drained by the\nstale entry) or, if the count leaked high for any other reason, skip\nthem all. usbd_reset() now zeroes the counter; the consumer already\nguards on zero, and any pre-reset SETUP still in the queue is stale by\ndefinition and correctly discarded.\n"
    },
    {
      "commit": "a0249ada9096365697340031a7b4a285beb18a2b",
      "tree": "a860c416a256718d4e2a5ef0c623bbc32fa0977b",
      "parents": [
        "32530d8f4b6cd6d6f7a7df7e6358856c7ebc4f5e"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 12 22:36:49 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Wed Aug 12 22:41:17 2026 +0700"
      },
      "message": "usbd: don\u0027t leak the queued-setup counter when the event queue is full\n\nA SETUP arriving while the event queue is full is silently dropped by\nqueue_event(), but _usbd_queued_setup has already been incremented. The\nleaked count makes the event handler skip every subsequent SETUP\n(\"Skipped since there is other SETUP in queue\") forever: EP0 stays deaf\nuntil tud_init() while the device otherwise looks alive - enumerated,\nendpoints armed. Undo the increment when the enqueue fails.\n\nUnit test: fill the queue so a SETUP is dropped, then verify the next\nSETUP still completes a GET_DESCRIPTOR control transfer.\n"
    },
    {
      "commit": "73e787ae418df76b79ac0fd31bb0674a72e68c88",
      "tree": "0a9c444705b8489e8d812e045b8bca5d8621f9a1",
      "parents": [
        "32530d8f4b6cd6d6f7a7df7e6358856c7ebc4f5e"
      ],
      "author": {
        "name": "Ryzee119",
        "email": "wendland@live.com.au",
        "time": "Wed Aug 12 20:40:15 2026 +0930"
      },
      "committer": {
        "name": "Ryzee119",
        "email": "wendland@live.com.au",
        "time": "Wed Aug 12 20:41:28 2026 +0930"
      },
      "message": "ohci: fix double allocation of dummy TDs in gtd_find_free\n"
    },
    {
      "commit": "32530d8f4b6cd6d6f7a7df7e6358856c7ebc4f5e",
      "tree": "547c03d3053de8ab11de078853410ce6bbf5dc70",
      "parents": [
        "4ab970f0a7c4ba06d3170b59e21e1a42c866e995",
        "d0f8c75edd3f6f05792976dbdbb0bc21f4d8ed39"
      ],
      "author": {
        "name": "Zixun LI",
        "email": "admin@hifiphile.com",
        "time": "Fri Aug 07 11:36:51 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 07 11:36:51 2026 +0200"
      },
      "message": "Merge pull request #3761 from morse-cedricvandenbergh/fix/ncm-link-state-notify-retry\n\nncm: retry link-state notification, fix carrier lost on collision"
    },
    {
      "commit": "d0f8c75edd3f6f05792976dbdbb0bc21f4d8ed39",
      "tree": "2ca260d473a2be23c22bb43aa1ee067939b4c742",
      "parents": [
        "cf055c237a93d3308e1670285dfd2b629f0dd8af"
      ],
      "author": {
        "name": "Cedric Van den Bergh",
        "email": "cedric.vandenbergh@morsemicro.com",
        "time": "Wed Jul 08 15:04:35 2026 +0100"
      },
      "committer": {
        "name": "Cedric Van den Bergh",
        "email": "cedric.vandenbergh@morsemicro.com",
        "time": "Fri Aug 07 07:42:32 2026 +0100"
      },
      "message": "test/fuzz: stub usbd_defer_func in net_ncm harness\n\nThe self-contained net_ncm fuzz harness #includes ncm_device.c and stubs\nthe usbd symbols it references rather than linking the device stack.\ntud_network_link_state() now calls usbd_defer_func(), so add a matching\nno-op stub to keep the harness linking.\n"
    },
    {
      "commit": "cf055c237a93d3308e1670285dfd2b629f0dd8af",
      "tree": "b3631db8eb6b968939cca888372c243b9cbd1478",
      "parents": [
        "fa750d6bf045f1df4314d283dfe0508d0d066559"
      ],
      "author": {
        "name": "Cedric Van den Bergh",
        "email": "cedric.vandenbergh@morsemicro.com",
        "time": "Wed Jul 08 12:47:03 2026 +0100"
      },
      "committer": {
        "name": "Cedric Van den Bergh",
        "email": "cedric.vandenbergh@morsemicro.com",
        "time": "Fri Aug 07 07:42:31 2026 +0100"
      },
      "message": "ncm: fix carrier lost on link-state notify collision\n\ntud_network_link_state() delivered the NETWORK_CONNECTION notification\nedge-triggered and fire-once: if a previous notification was still in\nflight, notification_xmit() returned early and the notification for the\nnew link state was never queued. Because link_is_up is committed before\nthe send, the host could be left reporting a stale carrier state - e.g.\na permanent NO-CARRIER after a link up. The notification state was also\nmutated from both the caller and the notify xfer-completion callback\nwith no serialisation, so on RTOS ports where tud_network_link_state()\nruns in a task other than tud_task() the two could race.\n\nDefer the whole link-state update onto the usbd task, so it can no\nlonger race the completion callback. A collision with an in-flight\nnotification is resolved by re-arming notification_xmit_state and\nletting the existing completion callback drive it forward on the next\nxfer completion, rather than adding a separate pending/retry flag.\n\nA link toggle does not change the link speed, so strictly only the\nNETWORK_CONNECTION notification needs (re)sending, but reusing the\nexisting speed-then-connection state machine keeps the fix on a single,\nalready-serialised code path.\n\nCloses #3760\n"
    },
    {
      "commit": "4ab970f0a7c4ba06d3170b59e21e1a42c866e995",
      "tree": "af477ed87f4cf569a46d36c10066f30d06b0a299",
      "parents": [
        "f3021b337fcea154b898489c417d428c92f88e92",
        "b0738b5949130d5ab175835d72bb793830d09bc9"
      ],
      "author": {
        "name": "Zixun LI",
        "email": "admin@hifiphile.com",
        "time": "Wed Aug 05 09:32:31 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 05 09:32:31 2026 +0200"
      },
      "message": "Merge pull request #3809 from rt-rtos/audio-fix-flow-control-fifo-guard\n\naudio: fix inverted FIFO-size guard in EP-IN flow control"
    },
    {
      "commit": "b0738b5949130d5ab175835d72bb793830d09bc9",
      "tree": "af477ed87f4cf569a46d36c10066f30d06b0a299",
      "parents": [
        "f3021b337fcea154b898489c417d428c92f88e92"
      ],
      "author": {
        "name": "rt-rtos",
        "email": "rasmus.tikkanen95@gmail.com",
        "time": "Tue Aug 04 21:09:20 2026 +0200"
      },
      "committer": {
        "name": "rt-rtos",
        "email": "rasmus.tikkanen95@gmail.com",
        "time": "Tue Aug 04 21:09:20 2026 +0200"
      },
      "message": "audio_device: enforce the documented FIFO minimum in the EP-IN flow-control guard\n\nThe comment above audiod_tx_packet_size() states flow control needs a FIFO\nof at least 4*Navg, but the guard tests nominal_size[1] \u003c\u003d fifo_depth * 4 -\ntrue for any FIFO larger than a quarter packet - instead of\nnominal_size[1] * 4 \u003c\u003d fifo_depth. As written, flow control engages on\nFIFOs far below its own documented minimum, where the depth/2 setpoint sits\nwithin one packet of empty and the packet_size \u003d 0 branch (a zero-length\npacket, i.e. an audible 1 ms dropout for audio-class hosts) is reachable\nfrom ordinary scheduling jitter rather than only from gross clock\ndeviation. With the guard corrected, undersized FIFOs fall back to the\nplain min(count, max) path as intended.\n"
    },
    {
      "commit": "f3021b337fcea154b898489c417d428c92f88e92",
      "tree": "7bcbffce06f8e7cf1217fc8749aeffa4cd1b64a0",
      "parents": [
        "eef5af86aa26fe3d72e41156a586a6ed3ffce9f8"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Fri Jul 31 23:17:36 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 31 23:17:36 2026 +0700"
      },
      "message": "test/hil: fold openocd_wch into openocd, verify per board, resolve firmware by flasher extension (#3804)\n\ntest/hil: one openocd flasher, per-board verify and firmware extension\n\nThe four WCH boards move to `openocd`, leaving one flasher for all.\n\n`verify` is now a per-board opt-out, not dropped fleet-wide: WCH cannot read flash back\nover the WCH-Link sdi transport; the other seven openocd boards can, and say so explicitly.\n\nFLASHER_SUFFIX decides each flasher\u0027s extension once — find_firmware returns the full path\nand the flashers pass it through, so a build with only the wrong artifact is skipped rather\nthan failed mid-flash. --skip-flash bypasses the filter.\n\nrescue_openocd() power-on-resets a wedged RP2040/RP2350 via its Rescue DP from the flash\nretry; the probe has no reset line.\n\nDrops unused openocd_adi, stflash, wlink_rs and uniflash, parks the unstable ra6m5_ek, and\ntests that every roster flasher name dispatches."
    },
    {
      "commit": "15dd3120ac4a9dea0d979dc541ef8e0f52f5aa26",
      "tree": "d17d56174bcd8862581075132cf921a0a291d783",
      "parents": [
        "eef5af86aa26fe3d72e41156a586a6ed3ffce9f8"
      ],
      "author": {
        "name": "Javid Khan",
        "email": "dxbjavid@gmail.com",
        "time": "Thu Jul 30 14:13:50 2026 +0530"
      },
      "committer": {
        "name": "Javid Khan",
        "email": "dxbjavid@gmail.com",
        "time": "Thu Jul 30 14:13:50 2026 +0530"
      },
      "message": "clamp committed video payload size to streaming ep buffer\n\nSigned-off-by: Javid Khan \u003cdxbjavid@gmail.com\u003e\n"
    },
    {
      "commit": "6271842ea874812f1157b42bcd650c59ab59259b",
      "tree": "fba445d655d77108700c519f3c8f1f7346a9318e",
      "parents": [
        "1b628e3fb581d56639fac9287448203db7d10ff1",
        "eef5af86aa26fe3d72e41156a586a6ed3ffce9f8"
      ],
      "author": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Jul 30 02:35:02 2026 +0700"
      },
      "committer": {
        "name": "hathach",
        "email": "thach@tinyusb.org",
        "time": "Thu Jul 30 02:35:02 2026 +0700"
      },
      "message": "Merge remote-tracking branch \u0027origin/master\u0027 into tmp/pr3790-merge\n"
    },
    {
      "commit": "eef5af86aa26fe3d72e41156a586a6ed3ffce9f8",
      "tree": "fa5ebe3a8d46738338eaee2d4a80717a7a10f464",
      "parents": [
        "5d8afbb23248e9d1d5d1fdf434c9c58cc54ba5b7"
      ],
      "author": {
        "name": "Ha Thach",
        "email": "thach@tinyusb.org",
        "time": "Thu Jul 30 02:29:32 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 30 02:29:32 2026 +0700"
      },
      "message": "hil, ci: scope HIL builds and tests to the boards a PR affects (#3797)\n\nhil, ci: scope HIL builds and tests to the boards a PR affects\n\nAdd test/hil/hil_select.py, a stdlib-only selector that maps a PR diff to the\nrig boards, tests and BSP families a change can affect, and wire it into CI so\npull requests build and run only those. A port change picks its families\u0027 boards,\na class change picks the examples enabling that class, and device/host changes\nprune the other role. Anything unclassified — infra, an unmapped port, a selector\nerror — falls back to the full matrix, and push/schedule runs are untouched.\nMove the shared example lists to hil_examples.py; 54 hardware-free tests cover\nthe rules."
    }
  ],
  "next": "5d8afbb23248e9d1d5d1fdf434c9c58cc54ba5b7"
}
