Note
This is a daily beta build (2026-04-14). It contains the latest fixes and improvements but may have undiscovered issues.
Docker users: Update by pulling the new image:
docker pull ghcr.io/maziggy/bambuddy:daily
or
docker pull maziggy/bambuddy:daily
**Tip:** Use [Watchtower](https://containrrr.dev/watchtower/) to automatically update when new daily builds are pushed.
New Features
- Move Build Plate from Printer Card (#791) — The printer card controls row now has a Z-jog badge between the speed control and the stop/pause buttons. Click the up/down arrows to move the build plate; click the middle label to switch the step size (1 / 10 / 50 mm). When the printer is not homed (typical right after a print finishes), the first jog opens a Bambu Studio-style warning modal with Home Z, Move anyway (bypasses soft endstops for this move), or Cancel. After the first "Move anyway" in a session, subsequent jogs skip the dialog. Disabled while a print is running. Backed by new
POST /printers/{id}/bed-jogandPOST /printers/{id}/home-axesendpoints, both gated behindprinters:control. Thanks to cadtoolbox for the request. - Printer Card Status Badges & Quick Controls — The Printers page printer card now exposes new at-a-glance controls inspired by the Home Assistant Bambu Lab integration:
- Enclosure Door badge in the top status row (DoorOpen/DoorClosed icons, green when closed, yellow when open). Detection uses the right MQTT field per printer family —
home_flagbit 23 on X1/X1C/X1E and the top-levelstathex string bit 23 on P1/P2/H2 — and falls through the existing WebSocket push (status-change dedup key now includes door state, so toggling the door alone triggers a live badge update without waiting for the 30 s REST poll). - Airduct Mode badge beside the print speed control (Snowflake/Flame icons, sky for Cooling and orange for Heating). One-click dropdown switches the printer between cooling and heating via the existing
set_airductMQTT command. Gated to P2S/H2D/H2C/H2S. - Force Refresh menu entry in the printer card kebab menu (RotateCw icon) that re-requests a full
pushallMQTT status report from the printer without forcing a reconnect.
- Enclosure Door badge in the top status row (DoorOpen/DoorClosed icons, green when closed, yellow when open). Detection uses the right MQTT field per printer family —
- AI Print-Failure Detection via self-hosted Obico ML API (#172) — New Settings → Failure Detection tab wires Bambuddy to a self-hosted Obico
ml_apicontainer (no Obico account, no cloud, no WebSocket). While a print is running, the detection service periodically hands the printer's camera snapshot URL to the ML API, which returns YOLO failure-detection scores. Scores are smoothed over time using Obico's own EWM + short/long rolling-mean math (30-frame warmup, alpha = 2/13, short window ≈ 5 min at 10s/frame, long window ≈ 20 h) so a single noisy frame cannot trigger an action. Sensitivity (Low / Medium / High) scales the LOW/HIGH thresholds; when the smoothed score crosses HIGH, the configured action runs exactly once per print: Notify only, Pause print (MQTT pause command), or Pause and cut power (pause + turn off any smart plug linked to that printer). A per-printer toggle lets you monitor all connected printers or just a subset. The Status card shows whether the service is running, the active thresholds, each monitored print's current verdict (safe / warning / failure), and a live rolling detection history. Requires that the External URL setting (General tab) points to a hostname/IP reachable from the ML API container, since the ML API fetches snapshots by URL.
Improved
- Firmware Update Modal Shows All Announced Versions (#568) — The firmware update dialog now lists every version announced on Bambu Lab's wiki release history, not just the single newest one. Each row shows whether an offline firmware file is actually available for that version — rows marked Usable (green) can be installed, rows marked Unavailable (gray) are announced but have no downloadable package yet (common for hot-fix releases like
01.01.03.00which Bambu only ships as OTA). The currently installed version is highlighted with a blue Installed badge. Selecting any usable row swaps the release-notes block at the top to that version's notes and enables the Install button for it — including older-than-current versions, so you can roll back to a previous firmware without having to hand-flash a file. The wiki scraper was tightened to only extract version numbers from heading anchors (e.g.id="h-01030000-20260303") so incidental version mentions in release-note prose — like an AMS firmware reference in an H2D changelog — no longer get mistaken for H2D firmware releases. Thanks to Cornelicorn for the request. - Spoolbuddy Device Controls in Settings (#962) — Each Spoolbuddy device card in Settings → Spoolbuddy now exposes five one-click actions alongside the existing Unregister button: Update (trigger daemon software update), Restart Browser (kiosk UI), Restart Daemon, Reboot (device), and Shutdown. Each action shows a confirmation dialog before queueing the command; buttons are disabled when the device is offline. Uses the existing
/spoolbuddy/devices/{id}/updateand/spoolbuddy/devices/{id}/system/commandendpoints — no new backend work needed. Thanks to TravisWilder for the request. - Support Bundle Covers All Settings & SpoolBuddy — The support bundle / bug-report payload now dumps every row in the
Settingstable instead of filtering by a hard-coded allowlist: sensitive keys (tokens, passwords, URLs, paths, emails, etc.) have their values replaced with[REDACTED]but the key itself is kept, so new config flags automatically show up in future bundles without a code change. Also adds anintegrations.spoolbuddysection listing registered SpoolBuddy devices (firmware version, NFC/scale hardware, calibration, online state, uptime) — anonymized, no hostnames/IPs/device IDs. - Settings Search Finds More Cards — The cross-tab search field at the top of Settings now finds Sidebar Links, Spoolman, Spool Catalog, Color Catalog, all four Failure Detection sections, Advanced Email Authentication, SMTP Test, Authenticator App (TOTP), Email OTP, 2FA Linked Accounts, Single Sign-On (OIDC), LDAP Server Configuration, and the four Backup sub-cards (GitHub, History, Local, Scheduled). Powered by a new module-level registry (
frontend/src/lib/settingsSearch.ts) so future settings register themselves next to their component instead of being forgotten in a central array.
Changed
- Plate-Clear Confirmation Disabled by Default — New installs ship with Settings → Workflow → "Require Plate-Clear Confirmation" off. Multiple new users reported queued prints appearing to not start because the prompt was waiting for acknowledgement; opt in from Workflow if you want the confirmation gate.
Fixed
- Obico ML API Got 401 When Fetching Snapshot with Auth Enabled (#172) — The Obico failure-detection service handed the ML API container a snapshot URL (
/api/v1/printers/{id}/camera/snapshot) for it toGETdirectly, but when Bambuddy authentication was enabled the endpoint returned 401 and the ML API surfaced "Failed to get image" (visible as a 400 from the ML API back to Bambuddy). The detection service now appends a short-lived camera-stream token to the snapshot URL — the same token scheme already used by<img>-based camera consumers, which the snapshot endpoint already accepts. The token is cached on the service and refreshed before its 60-minute expiry, so no extra per-call DB churn. When auth is disabled the token is simply ignored. Thanks to fblix for reporting. - Direct Print from Library Not Attributed to User — Clicking the Print button on a library file dispatched the job with no
created_by_id, so the resulting archive had no owner and the print didn't show up in per-user statistics. The Queue and Reprint paths already forwarded the authenticated user; the libraryPOST /files/{file_id}/printendpoint now does the same, reading the user from the JWT and passing it through to the dispatcher so direct prints are attributed like queued and reprinted ones. - Add/Edit Printer Modal Clipped on Short Viewports (#964) — On short or zoomed-in browser windows, the Add Printer and Edit Printer dialogs exceeded the viewport height with no scroll, hiding the lower fields (Access Code, Model, Location) and the Save button. Users had to zoom the browser out to complete the form. The modal overlay now scrolls and the card caps at
calc(100vh - 2rem)with internal overflow so every field stays reachable regardless of viewport height. Thanks to MartinNYHC for reporting. - AMS Drying Silently Does Nothing (#971) — Clicking Start Drying on a supported printer (e.g. P1S with AMS 2 Pro) could publish the MQTT command successfully but leave the AMS idle with no UI feedback. Two issues: (1) the firmware rejects the command when
dry_sf_reasonreports a blocking state (most commonly code 8 — AMS 2 Pro external power adapter not plugged in — but also "AMS busy", "already drying", etc.), and Bambuddy parsed that array but never surfaced it to the user; (2) the payload sentfilament: "", which some firmwares treat as an invalid-field refusal. The/drying/startendpoint now inspects the livedry_sf_reasonfor the target AMS unit and returns a descriptive 409 (e.g. "Plug in the external AMS power adapter to start drying") instead of silently publishing, and backfills an emptyfilamentfrom the first loaded tray's type (defaulting toPLA) so the printer never rejects the command for a missing field. Thanks to MartinNYHC for reporting. - Webhook Tokens Leaked into Logs When Debug Logging Enabled (Security) — Turning on Settings → Support → Debug Logging elevated the
httpxandhttpcoreloggers to DEBUG, which caused httpx to log the full URL of every outbound HTTP request. For Discord notifications and generic webhook notifications, the URL is the secret — the bearer token is embedded in the path — so any user who enabled debug logging (typically to capture logs for a bug report) was writing their Discord webhook token tobambuddy.logand then pasting it into GitHub issues or support bundles.httpx/httpcoreare now pinned toWARNINGregardless of the debug toggle;paho.mqttstill honours debug. If you enabled debug logging while notifications were sending, rotate any exposed Discord/webhook URLs — the token is in the path, so the whole URL must be regenerated in the provider's UI. - Queue Item Stuck in "Printing" When Start Command is Dropped (#967) — If the physical printer dropped or ignored the MQTT
project_filestart command (same half-broken-session shape as #887/#936), the queue item was permanently orphaned in theprintingstatus at 100% because the scheduler optimistically flipped the DB row toprintingright after the publish succeeded locally and had no watchdog to revert it. Recovery required manually editing the SQLiteprint_queuetable. A new watchdog now captures the printer's pre-dispatch state and polls for up to 45 s afterstart_print()returns; if the printer never transitions, the item is reverted topendingso the scheduler picks it up again, and the MQTT session is force-reconnected so the retry lands without a printer reboot. Thanks to stringham for reporting. - Queued Prints Require Printer Reboot to Start (#936) — On some printers, a queued print would be uploaded via FTP and the
project_fileMQTT command would be sent, but the printer never transitioned out ofFINISH/IDLEand required a power cycle to unstick — after which it often started a previously cancelled print rather than the intended one. Root cause is a half-broken MQTT session (same shape as #887): the printer keeps publishing telemetry so Bambuddy reports it as connected, but our publishes on the command topic never reach the firmware. Existing recovery only triggered via the developer-mode probe path, which skips printers that already have a knowndeveloper_modevalue. The print-dispatch verifier now treats an unacknowledgedproject_file(state unchanged after 15 s) as the same "commands not reaching printer" signal and forces a fresh MQTT session so the next dispatch can land without a printer reboot. The existing dev-mode probe path is refactored to share the same helper. - Clear Plate Confirmation Bypassed on Power Cycle (#961) — With Auto Off enabled and another job queued, the smart plug would cut power when a print finished and immediately re-power when the scheduler saw the queue, at which point the printer booted fresh into
IDLEand the next job auto-dispatched without the "Clear Plate & Start Next" confirmation. Root cause: the plate-cleared gate lived only in the in-memoryPrinterManager._plate_clearedset, and the scheduler's idle check treatedIDLEas always-idle regardless of whether a previous finish had been acknowledged — so the gate was lost across both Bambuddy restarts and the IDLE-on-boot state transition. The gate is now anawaiting_plate_clearcolumn on theprinterstable, set byon_print_completewhen a print finishes or fails, cleared by the/printers/{id}/clear-plateendpoint and by the scheduler when it dispatches the next job, and rehydrated from the DB intoPrinterManageron startup._is_printer_idlenow short-circuits to not-idle wheneverrequire_plate_clearis on and the printer is awaiting ack, regardless of the currently reported state — so the prompt survives Auto Off cycles, Bambuddy restarts, and the printer booting back intoIDLE. The clear-plate endpoint no longer requires the printer to currently reportFINISH/FAILED(it accepts the ack whenever the awaiting flag is set), and the Printers page widget prompts based on the flag rather than the reported state. Thanks to miaopas for reporting. - Insecure Temp File Creation in Backup Export — The manual backup download endpoint used
tempfile.mktemp(), which is vulnerable to a symlink race condition (CWE-377). Replaced withtempfile.mkstemp()which atomically creates the file, eliminating the TOCTOU window. - Spoolman Iframe Blocked After 0.2.3b4 Security Headers — The Spoolman page (Inventory → Spoolman iframe) failed to load when Spoolman was served from the same host as Bambuddy via a reverse proxy. The security-headers middleware added in 0.2.3b4 set
X-Frame-Options: DENYon every response, which blocked even same-origin iframing. Relaxed toSAMEORIGINso Spoolman (and any other same-origin tool behind the same reverse proxy) can be embedded again, while still preventing cross-origin clickjacking. - SD Card Badge Removed — After four rounds of fixes the printer-card SD status badge still flipped red on H2D when unrelated activity happened on the network (e.g. powering on an A1 caused every H2D to go red simultaneously). The underlying problem is that Bambu firmware SD-state signaling is not reliably derivable from MQTT: the legacy top-level
sdcardfield is only sent on some pushes with inconsistent typing, andhome_flagbits 8-9 are cleared on heartbeat pushes even when a card is inserted, with no reliable way to distinguish heartbeats from full status reports. The badge has been removed entirely from the Printers page card and the Printer Info modal. Underlyingstate.sdcardparsing is retained (simplified to a plain truthy read of thesdcardfield only, no morehome_flagderivation, no heartbeat latches) because the firmware-update precondition check still needs to know whether a card is inserted before starting an update. Thanks to MartinNYHC for the extensive reporting across all four rounds. Previously, this entry described the H2D badge flap and its three attempted fixes — kept here for history: The original bug toggled between "inserted" (green) and "not inserted" (red) every few seconds on H2D. Root cause: the MQTT parser used a strict identity check (data["sdcard"] is True) on the top-levelsdcardfield, but real firmware ships that field inconsistently — bool on some models, int1, or a string enum like"HAS_SDCARD_NORMAL"on others — so any message carrying a non-bool value flipped the state toFalse. Fixed by deriving the badge fromhome_flagbits 8–9 (HAS_SDCARD_NORMAL/HAS_SDCARD_ABNORMAL) when present — the canonical firmware source, same as door and store-to-SD parsing — and falling back to a truthy check on the top-level field for firmwares that only send that. Follow-up: the badge was still flapping because Bambu firmwares send partial MQTT pushes that carry the legacysdcardfield alone (withouthome_flag), and the fallback was re-engaging on every such push. The parser now latcheshome_flagas the canonical source for the session once seen, so partial pushes carrying onlysdcardcan no longer flip the badge; the latch resets on reconnect so a firmware change still re-learns. Second follow-up: on H2D the badge still showed red on initial Printers-page navigation and flipped to green on reload, because H2D also sends heartbeat-stylehome_flagpushes where bits 8–9 are clear even when a card is inserted. Downgrades from true→false now require three consecutive clear reads (upgrades false→true still apply immediately), so a single heartbeat no longer turns the badge red. Third follow-up: the three-strike counter still lost the race on idle printers — once an A1 or other printer connecting nearby triggered a burst of MQTT activity, idle H2Ds could accumulate ≥3 heartbeat pushes before the next full status report and all flip to red simultaneously. Reworked the derivation: the legacy top-levelsdcardfield is now authoritative when present (truthy check covers bool/int/string firmware variants),home_flagbits 8–9 are only consulted on fullpush_statusreports (identified by the presence of multiple state markers likegcode_state,mc_percent,nozzle_temper,print_type,stg_cur, orams), and bare heartbeat pushes carryinghome_flagalone no longer affect SD state at all. Thanks to MartinNYHC for reporting. - CSP Blocked Sidebar Iframes, Service-Worker Registration, and Google Fonts — The strict
Content-Security-Policyheader added in 0.2.3b4 broke three things at once: (1) custom sidebar links pointing at external HTTPS URLs (e.g. a Grafana/telemetry dashboard) rendered inExternalLinkPagewere blocked because noframe-srcwas declared and iframes fell back todefault-src 'self'; (2) the inline service-worker registration<script>at the bottom ofindex.htmlwas blocked byscript-src 'self', silently preventing the PWA service worker from installing; (3) theimportof Google Fonts' Inter fromindex.csswas blocked bystyle-srcandfont-src. Fixed by addingframe-src 'self' https:for user-configured HTTPS iframe targets, moving the inline SW-registration script into/sw-register.jssoscript-src 'self'covers it without needing'unsafe-inline'or per-build hashes, and allowinghttps://fonts.googleapis.cominstyle-srcandhttps://fonts.gstatic.cominfont-src.frame-ancestors 'none'is preserved so Bambuddy itself still cannot be framed cross-origin.