Bitaxe – Pool Difficulty Change with cleanJob=false Not Handled
Informational — Monitor and address as needed
Symptoms
- AxeOS dashboard reads close to nameplate hashrate, but the pool dashboard shows effective hashrate 20-60% below what the device reports
- Pool dashboard shows a non-zero stale shares or stale rejected counter that climbs steadily — slow tick every 30-120 seconds, not a single burst
- Pool log shows Share rejected: stale share or Share above target messages with timestamps tightly clustered around difficulty changes
- Serial console at 115200 baud shows multiple mining.notify lines arriving without a corresponding mining.set_difficulty being applied, or set_difficulty arriving but the next submit going out at the previous difficulty
- AxeOS log shows the message incompatible job_id (or pool-equivalent error) on share submit, particularly right after a difficulty bump
- Difficulty value displayed in AxeOS does not match what the pool's worker page shows for the current minute
- Behaviour is pool-specific — same Bitaxe on a different pool (Public-Pool, CKPool) shows zero stale shares, but on NiceHash, a commercial proxy, or a vardiff-aggressive endpoint stale shares climb
- Behaviour appeared after a firmware OTA, a fork swap (community build to mainline or vice versa), or a pool-side change in vardiff aggressiveness
- Bitaxe Hex specifically: one or more chip domains show fewer accepted shares per chip than the others, despite even hashrate distribution
- Solo lottery context: AxeOS local 'best difficulty' tracker continues to climb, but pool-credited difficulty per share looks lower than the AxeOS dashboard difficulty
- Share submission flow looks healthy in the log (mining.submit going out, results coming back) but the result is false with reason stale more often than the historical 0-0.2% baseline
Step-by-Step Fix
Update AxeOS firmware to the current stable mainline release. Open AxeOS in a browser on the same LAN, navigate to System -> OTA Update, and apply the latest stable. If OTA is broken or you want to pin to a specific known-good build, use the USB-C web flasher at https://bitaxe.org/flash with Chrome or Edge (WebSerial required). Pick the exact board revision before flashing — Ultra 205 vs 207, Gamma 601 vs 602, Hex 303 vs 304, Gamma Turbo XX. Wrong .bin for board revision can brick the device. Reboot fully (10-second power cycle), then watch pool-side effective hashrate for 60 minutes.
Switch the pool URL to a cleanJobs=true-defaulting endpoint. The cleanest test pool is Public-Pool (public-pool.io, port 21496 plain TCP or 21497 SSL). Solo-CKPool (solo.ckpool.org:3333) and Ocean's solo endpoint (mine.ocean.xyz:3334) are equivalent. All three default to cleanJobs=true on every mining.notify and re-tune difficulty conservatively, sidestepping the race entirely. Save the change in AxeOS, reboot the Bitaxe with a hard power-cycle, and observe pool effective hashrate over 60 minutes. If the gap to local hashrate closes, your previous pool's vardiff aggressiveness was the trigger.
Remove any stratum proxy from in front of the miner. If you're running bos-toolbox, nerdminer-proxy, a custom CKProxy, or any kind of stratum-aggregating layer between the Bitaxe and the actual pool, point the Bitaxe directly at the pool instead. Proxies can reorder set_difficulty and notify in ways that are technically valid in protocol terms but trigger the Bitaxe race more often. This is a temporary diagnostic — once you've confirmed the proxy is the trigger, you can either accept the trade-off or wait for the firmware fix.
Cold power-cycle the Bitaxe after every settings change. Unplug USB-C (or the 12V input on Hex / GT) for a full 10 seconds, then reconnect. AxeOS exponential backoff on stratum reconnect can mask whether your settings actually took effect, and a clean power cycle clears any wedged state machine. A warm reboot from the AxeOS UI is not as thorough — the FreeRTOS tasks survive a soft reboot in some firmware versions, including the stratum task that's the focus of this bug.
Document your firmware version and exact pool URL/port for repeatability. AxeOS occasionally factory-resets after specific failure modes, and you don't want to lose institutional memory of 'this firmware on this pool was the problem combination.' Note it in a password manager, a notes app, anywhere outside the device. Solo-mining operators in particular benefit from this — every parameter that affects shares matters when your tickets are 1-in-many-trillions.
Capture the USB-C serial log at 115200 baud during a stale-share burst. Plug a USB-C data-capable cable (not charge-only) into the Bitaxe, open a serial terminal (screen, minicom, PuTTY, or Chrome WebSerial), and watch the stratum_api task output. Filter for notify, set_difficulty, submit, result, stale, incompatible. The pattern: a set_difficulty line, immediately followed by a notify with cleanJobs=false, immediately followed by a submit whose result comes back as false with reason:stale. Save 5-10 minutes of log.
Compare pool-reported difficulty against AxeOS-reported difficulty in real time. Open the pool's worker dashboard (Public-Pool worker page, CKPool worker page, Ocean worker page) and AxeOS at the same time. Watch both for 5 minutes. The two should track within one stratum round-trip of each other. If AxeOS is showing a difficulty that's two or three steps stale relative to the pool, the firmware is buffering difficulty updates incorrectly — that's the bug, observed live. Screenshot both and timestamp the comparison.
Try a different community fork or a different stable release of mainline. If you're on a fork, try mainline (if your board is supported). If you're on mainline current stable, try the previous stable — sometimes a regression sneaks into a current release, and rolling back one minor version is a valid diagnostic step. Use the USB-C web flasher and select the older .bin from the ESP-Miner releases page. Re-test on a cleanJobs=true-defaulting pool first to rule out other variables.
Run mtr <pool-hostname> from a laptop on the same LAN. Packet loss above 1-2% on the Bitaxe to pool path can compound the symptom: mining.set_difficulty packets dropped by the network look identical, at the firmware layer, to set_difficulty packets that arrived but were processed in the wrong order. Fix the network path first, then re-evaluate. Hop-1 loss = router/WiFi (move closer to AP, lock to 2.4 GHz channel 1/6/11 — the ESP32-S3 is 2.4 GHz only). Hop-2 to hop-5 loss = ISP egress; consider a closer pool endpoint.
Override router DNS to 1.1.1.1 (Cloudflare) and 8.8.8.8 (Google). Canadian residential ISPs (Bell, Rogers, Vidéotron, Cogeco, Shaw) have flaky default resolvers that occasionally return slow or wrong IPs for stratum hostnames, and a slow DNS lookup early in the stratum lifetime can compound into unusual ordering of subsequent JSON-RPC messages. This isn't the root bug, but it's a confounding variable to remove before declaring 'firmware bug' with confidence.
Capture a 10-minute Wireshark trace of the stratum exchange. Run Wireshark on a laptop plugged into the same LAN — ideally a managed switch with port mirroring; otherwise an ARP-spoof on the Bitaxe's gateway to pull traffic. Filter tcp.port == <pool-port> and ip.addr == <bitaxe-ip>. Save as .pcapng. Look at every mining.notify and the cleanJobs boolean value. Look at every mining.set_difficulty and where it falls relative to the surrounding mining.notify messages. Document the patterns. This is the proof artifact you'll attach to any upstream issue.
Build ESP-Miner from source with extra logging in stratum_api.c and stratum_task.c. Clone bitaxeorg/ESP-Miner (or the relevant fork), add ESP_LOGI traces around the difficulty-update and notify-enqueue functions, build with ESP-IDF (current LTS), flash via USB-C. The extra log lines should pinpoint exactly where the state machine is reading the difficulty value relative to enqueueing the notify. Document your findings, file an issue with a reproducer, and (Mining Hacker move) submit a PR if you've got the fix.
Test against a local CKPool / public-pool fork. Spin up CKPool or public-pool on a Raspberry Pi or old laptop, point the Bitaxe at 192.168.x.y:3333, and configure the local pool with predictable static difficulty so you can isolate the variable. If the bug doesn't reproduce against a known-tame stratum endpoint, the trigger is on the original pool's side (vardiff aggressiveness, proxy reordering, custom non-standard behaviour). If it does reproduce locally with predictable difficulty changes, you've got a perfect reproducer for the upstream issue.
Verify your board revision matches the firmware .bin exactly. The Bitaxe ecosystem has board revisions that look identical visually but differ in chip variant, flash size, or pinout. BM1366 firmware on a BM1370 Gamma will hash and connect, then misbehave at higher protocol layers in ways that look like stratum bugs. Read the silkscreen on the PCB (Ultra 205, Ultra 207, Gamma 601, Gamma 602, Hex 303, Hex 304, GT-XX) and cross-check against the bitaxe.org/hardware reference page before flashing.
Audit any community fork you're running for divergence from mainline. If you're on shufps/ESP-Miner-NerdQAxePlus or any other fork, check the last commit date on main/tasks/stratum_task.c and main/stratum_api.c against the same files in mainline. If the fork hasn't picked up mainline fixes from the last 60 days, you may be inheriting an older, buggier stratum task. File an issue in the fork's repo asking for a rebase, or rebuild the fork from a fresh sync to mainline if you can.
Ship the Bitaxe to D-Central if the symptom persists across firmware updates, pool changes, factory flash, and exhaustive diagnostics, AND you suspect a hardware-level issue compounding the protocol bug. The classic compound case: a Bitaxe with marginal ESP32-S3 PSRAM that occasionally drops or corrupts a buffer, and the corrupted buffer happens to be the active-difficulty pointer. From the operator's perspective, it looks identical to 'firmware ignores set_difficulty.' From the bench's perspective, it's a memory-integrity issue. D-Central pioneered the Bitaxe Mesh Stand and the first heatsinks, stocks every variant, and runs bench rigs specifically for stratum-path diagnostics.
D-Central bench process for this class of Bitaxe issue: factory-flash the latest mainline AxeOS, test against a controlled local CKPool with known difficulty changes and cleanJobs patterns, capture Wireshark + serial in parallel to confirm protocol-level behaviour. If hardware checks out and the symptom doesn't reproduce on the bench, the issue was your operating environment — pool, network, or proxy. If hardware does have an issue (PSRAM, ESP module, marginal TPS546), we swap the part on the bench and bench-validate before shipping back. Turnaround 5-10 business days, Canada-wide and international.
Ship safely to D-Central. Anti-static bag for the Bitaxe board, padded box with at least 5 cm of foam on every side. Include a note with: AxeOS version, board revision (read off the silkscreen), pool URL and port, observed stale-share rate, and any forks you've tried. The note saves us 30 minutes of intake diagnostics — which saves you money. Include the Mesh Stand and PSU if those are part of your setup; sometimes the issue is the PSU under load and we can validate it on the bench too.
When to Seek Professional Repair
If the steps above do not resolve the issue, or if you are not comfortable performing these repairs yourself, professional service is recommended. Attempting advanced repairs without proper equipment can cause further damage.
Related Error Codes
Still Having Issues?
Our team of Bitcoin Mining Hackers has been repairing ASIC miners since 2016. We have seen it all and fixed it all. Get a professional diagnosis.
