TuyaOpen IDE Preview — Bug Submissions
2026-08-23T06:40:00Z
Prepared for the TuyaOpen IDE Preview Tester Program (Bug Submission & Feedback phase).
Two parts:
- Part A — 7 device/platform-level {bugs-cum-suggestion} found while building a real product end-to-end.
- Part B — 14 source-level bugs found by auditing the SDK tooling itself
(tools/cli_command/*.py,export.*, IDE-generated wrappers). Each has exact
file:linereferences; B11 is reproduced live on this machine with before/after proof.
Global environment (applies to every report below)
Expand
| Field | Value |
|---|---|
| OS | Windows 11 Pro, PowerShell 5.1 (also reproduced in cmd.exe) |
| IDE | VS Code + Antigravity IDE with TuyaOpen IDE Preview extension v0.1.5 |
| SDK | TuyaOpen SDK main branch (IDE-managed install) |
| Toolchain | Python 3.12.13 (SDK-managed), GNU Make 4.4.1 |
| Hardware | Tuya T5AI-Core development board (Beken BK7258, Wi-Fi + audio AI) |
| Test project | Voice-first AI study assistant (base framework): AI audio chat, desk double-knock wake, LED indicator, app-controlled focus timer |
| Cloud side | Tuya Platform product PID okqfzw6tkrabylcs, Lume AI Agent (Study Assist) project bound to the product, Ray panel v1.0.0 |
Labels used: preview-tester bug (add ux / feature-request where noted)
BUG A1 — Windows build fails at packaging: 'charmap' codec can't encode character
Labels: preview-tester bug Severity: High (blocks all CLI/scripted builds)
Environment
Windows 11 Pro · TuyaOpen IDE Preview v0.1.5 · SDK main · Python 3.12.13 · T5AI-Core.
Reproduction steps
- Create a TuyaOpen project (any framework, any board).
- Open a plain PowerShell terminal (not the IDE-integrated terminal).
cd source/<project>and runpython <SDK_ROOT>\tos.py build.
Expected behavior
Build compiles and packages the firmware image.
Actual behavior
Build fails during the packaging step:
UnicodeEncodeError: 'charmap' codec can't encode character ...
Python defaults to the legacy cp1252 codepage on Windows unless explicitly told otherwise.
The IDE-integrated terminal happens to work, so the failure only appears when building
from scripts, CI shells, or a fresh terminal.
Evidence
Terminal capture of failed build, then successful build after workaround (screenshots attached to forum post).
Workaround found
$env:PYTHONUTF8 = "1"
$env:PYTHONIOENCODING = "utf-8"
must be set before every external build.
Suggested fix
Set UTF-8 mode inside tos.py itself (e.g. re-exec with -X utf8) or document the
requirement prominently. Best: make packaging codepage-independent.
BUG A2 | SUGGESTION — T5AI-Core has no auto-reset circuit: flash tools hang waiting for the device
CASE(Only when): The Boot time is highly reduced either due to very good network connection or less complex firmware logic.
-Causing The Board to boot , connect instantly -leaving user no timw windows to debug again. [only case left is to hard reset.]
Labels: preview-tester bug ux Severity: Medium (first-run blocker)
Reproduction steps
- Connect T5AI-Core over USB.
- Start flashing from tyutool GUI (or
tyutool_cli --plain write -d t5 -p COMx -f app.bin). - Do not touch the board.
Expected behavior
Tool enters the bootloader automatically (as ESP32-class boards do via DTR/RTS auto-reset).
Actual behavior
Tool sits in handshake until you press the board RST button during the handshake window
(or power-cycle at the right moment). Nothing on screen explains this — first-time users
conclude flashing is broken.
Verified working recipe (for docs)
tyutool GUI, correct port @ 460800, click flash → press RST immediately →
Verification passed. Flash succeeded. in ~1m31s.
Suggested fix
Show an explicit prompt/countdown in GUI+CLI (“Press RST now…”), detect the missed
handshake window, and document the manual-reset requirement per board.
BUG A3| SUGGESTION — USB serial port roles swap between sessions (log port ↔ flash port)
Labels: preview-tester bug ux Severity: Medium
Reproduction steps
- Note which COM port is the log console and which is the flash port
(USB-Enhanced-SERIAL-A vs -B). - Reboot the PC or replug the board several times.
Expected behavior
A stable, documented mapping between physical USB roles and COM ports.
Actual behavior
The roles swap unpredictably between sessions (observed both A=log/B=flash and the reverse).
Flashing to the “log” port times out; monitoring the “flash” port shows nothing.
Suggested fix
Label ports by role in tooling (read USB interface descriptor rather than enumeration order)
and show friendly names (“Flash”, “Log”) in GUI dropdowns.
BUG A4 — Device boots in HOLD conversation mode despite firmware defaulting to FREE
Labels: preview-tester bug Severity: Medium
Environment
Firmware uses AI audio components; conversation-mode DP 9 (hold/key/weakup/free) persisted via KV storage.
Reproduction steps
- Fresh flash; never touch conversation-mode DP.
- Boot device; try wake-word / hands-free interaction.
- Check KV / serial log.
Expected behavior
Device boots with the firmware-declared default (free), or clearly documents boot behavior.
Actual behavior
KV stores {"volume":86,"chat_mode":0} → mode 0 = HOLD, overriding the code default.
Hands-free doesn’t work until the user opens the app and sets DP 9 = free. Nothing tells
the user why the device “isn’t listening”.
Suggested fix
First-boot initialization should write the firmware default into KV (or distinguish
“never set” from “explicitly set”), and the panel should highlight non-free modes.
BUG A5 | SUGGESTION — Focus-timer start plays the Chinese power-on greeting instead of a dedicated chime
Labels: preview-tester bug ux Severity: Low
Reproduction steps
- Set the study focus timer (value DP) from the panel.
- Listen to the start feedback.
Expected behavior
A short neutral “timer started” tone/alert.
Actual behavior
The firmware reuses AI_AUDIO_ALERT_POWER_ON, which plays the localized power-on greeting
(Chinese TTS: “我在这里,我们一起玩吧”-style line). Users think the device rebooted or misheard.
Suggested fix
Provide a dedicated TIMER_START / TIMER_END entry in the alert enum (current enum:
POWER_ON, NOT_ACTIVE, NETWORK_CFG, NETWORK_CONNECTED, NETWORK_FAIL, NETWORK_DISCONNECT,
BATTERY_LOW, PLEASE_AGAIN, LONG_KEY_TALK, KEY_TALK, WAKEUP_TALK, RANDOM_TALK, WAKEUP).
BUG A6 | SUGGESTION— AI agent hallucinates device state (claims mute/LED/volume/timer control without ground truth)
Labels: preview-tester bug feature-request Severity: Medium (trust issue)
Reproduction steps
- Ask the agent: “mute the device” / “turn on the LED” / “how much time is left?”.
- Compare answer with actual device/panel state.
Expected behavior
Agent either performs the action via a registered tool/DP or honestly says it can’t.
Actual behavior
Without device-side state injection, the agent invents plausible-sounding answers
(“I’ve muted it for you”) while nothing changed. Only after we injected strict prompt rules
(“never claim device state; say ‘check the app’”) did responses become honest.
Suggested fix
Platform-level grounding: expose current DP snapshot to the agent context automatically,
or provide a standard “device state” tool-calling hook so agents stop hallucinating.
BUG A7 — Transient MQTT Cmd Parse Fail:-25344 during agent chat
Labels: preview-tester bug Severity: Low (recovers, but noisy)
Reproduction steps
- Use AI voice chat repeatedly over several minutes.
- Watch serial/console logs.
Expected behavior
Clean message flow, errors only on real faults.
Actual behavior
Intermittent Cmd Parse Fail:-25344 entries mid-session; session recovers on its own.
No user-visible effect observed, but the error code isn’t documented anywhere, making triage hard.
Suggested fix
Document error codes (-25344 etc.) in the SDK, and downgrade known-transient parse events to debug level.
PART B — Source-level bugs (SDK tooling audit)
All findings below come from reading the shipped tooling source in the installed SDK
(tools/cli_command/*.py, export.ps1/bat/sh, IDE-generated wrappers under
.tuyaopen/ide/bin/). Line numbers refer to the current main install.
B1 — Unquoted paths & user strings interpolated into shell=True commands: builds break on paths with spaces; project name is a command-injection surface
Labels: preview-tester bug Severity: High
Locations
tools/cli_command/cli_build.py:188—cmd = f"cmake -G Ninja {open_root} "— SDK root unquoted.tools/cli_command/cli_build.py:218-227—-DTOS_PROJECT_ROOT={app_root}and all other defines unquoted, then executed viado_subprocess()→subprocess.call(cmd, shell=True)(util.py:421).tools/cli_command/cli_build.py:137,168-170—{chip},{project_name} {platform} {framework} {chip}passed unquoted tobuild_setup.py/platform_prepare.py.tools/cli_command/cli_new.py:309—python {porting_script} {new_platform_path} {new_platform_name}unquoted.- Irony:
do_subprocess()'s own docstring (util.py:399-406) says it was rewritten so that “paths containing spaces stop breaking” — onlycwd=got fixed; every interpolated argument is still raw.
Impact
- Space paths: SDK installed under e.g.
C:\Users\John Doe\TuyaOpenSDKor a project dir likeMy Projects\app→ cmake receives split args → configure fails with cryptic errors. Non-ASCII usernames (very common outside the US) hit this too. - Injection:
CONFIG_PROJECT_NAMEcomes from user-editable Kconfig. A name likex&calcorx" & calc &"is spliced into acmd.exe /ccommand line. It’s the developer’s own machine, but any tool/agent that writes project names into configs (the IDE’s AI generation does!) turns this into arbitrary command execution during build.
Reproduction
Set CONFIG_PROJECT_NAME="a b" (or install SDK under a path with a space) → tos.py build.
Suggested fix
Use subprocess.run(list_args, shell=False) everywhere (the codebase already does list-form correctly in _tool_runs_ok, cli_prepare.py:244). At minimum, quote every interpolation.
B2 | FIXATON — GNU Make downloader: no checksum AND a poisoned archive cache permanently bricks builds
Labels: preview-tester bug Severity: High
Location
tools/cli_command/cli_prepare.py:139-227 (ensure_windows_make).
Bug 2a — No integrity check on downloaded make
The archive is fetched from a hardcoded CDN GUID URL (images.tuyacn.com/rms-static/f7061a90-….zip) and extracted with no SHA-256 verification — inconsistent with util_tyutool.download_tyutool_bin(), which does verify sha256. Whatever bytes the CDN returns get executed as make.exe.
Bug 2b — Corrupt cache = permanent failure loop (reproduced by code path)
if not os.path.isfile(archive_path): # cli_prepare.py:177
if not _download_file(...): return False # partial download → file EXISTS on disk
...
with zipfile.ZipFile(archive_path, "r") ... # next run: "Reusing archive" → BadZipFile
_download_file() (util_tyutool.py:142-186) streams straight to the final path; a timeout
(DOWNLOAD_TIMEOUT, flaky Wi-Fi, VPN drop) leaves a truncated zip cached forever. Every later
build then crashes with an unhandled zipfile.BadZipFile traceback — there’s no except
clause around extraction — and self-healing never happens because the file-exists check skips
redownload. User must manually find and delete .tools/archives/make/4.4.1/…zip.
Suggested fix
Pin + verify sha256 (like tyutool), download to .tmp then atomic-rename, and treat any
extraction failure by deleting the cached archive.
B3 — Build temporarily rewrites the user’s GLOBAL git config (and a crash leaves China mirrors permanently)
Labels: preview-tester bug Severity: Medium-High
Location
tools/cli_command/util_git.py:34-67 (set_repo_mirro) called from cli_build.py:101-112.
Behavior
When timezone==UTC+8, before cloning a platform repo the tool runs
git config --global url."https://gitee.com/tuya-open/X".insteadOf https://github.com/tuya/X
for 13 repos, then unsets them in a finally: block.
Problems
- Global side effect: while any build runs, the user’s other terminals silently redirect clones of those GitHub repos to gitee. Nothing tells the user their
~/.gitconfigis being rewritten. - Dirty state on hard kill:
finallycovers exceptions, but a killed process / IDE restart / power loss mid-clone leaves all 13insteadOfentries permanently. Weeks later, unrelated clones oftuya/TuyaOpen-T5AIstill go to gitee and the user has no idea why. - Parallel builds race: two shells building simultaneously → one unsets while the other still needs it.
Suggested fix
Never touch global config: pass mirror URLs directly to git_clone() (it already has resolve_effective_git_url()), or use GIT_CONFIG_COUNT/GIT_CONFIG_KEY_n env vars scoped to the child process only.
B4 — tos.py flash without -p targets an arbitrary serial port (this is why flashing “hangs”)
Labels: preview-tester bug ux Severity: Medium
Location
tools/cli_command/cli_flash.py:309 — if port: → with no -p, the port flag is simply omitted and tyutool picks its default (first enumerated COM port). On T5AI-Core there are TWO USB-CDC ports per board (console vs flash), so auto-pick frequently selects the log console, where flashing hangs forever.
Compare cli_monitor.py:_choose_port() (lines 170-191): monitor does prompt when multiple ports exist — but prints only bare device names:
print(f"{i+1}. {p}") # p.device only ("COM3"), no description!
serial.tools.list_ports provides .description (e.g. “USB-Enhanced-SERIAL-B (CH34x)”) which would instantly disambiguate console vs flash.
Suggested fix
Flash should reuse a port chooser that shows descriptions (and mark which ports look like flash-capable interfaces); monitor should print .description. One-line fix each.
B5 — Official entry points (export.bat/ps1/sh) don’t set UTF-8, so Bug A1 hits every non-IDE shell
Labels: preview-tester bug Severity: Medium (upgrades A1 from “workaround needed” to “fix belongs here”)
Grepped all three export scripts: no PYTHONUTF8, no PYTHONIOENCODING, no chcp.
export.* is documented as the mandatory entry point, yet the packaging step still crashes on
default cp1252 Windows shells. Two lines in export.* would kill the entire bug class:
$env:PYTHONUTF8 = "1"; $env:PYTHONIOENCODING = "utf-8"
(Or launch tos.py via python -X utf8.)
B6 — Asymmetric encoding in env store: read utf-8, write cp1252
Labels: preview-tester bug Severity: Low-Medium
tools/cli_command/util.py:347 — env_write() opens .cache/.env.json with
open(env_json, 'w') (no encoding) while env_read() (line 323) uses encoding='utf-8'.
Any stored value containing a non-cp1252 character (Chinese path segment, emoji in a
project field…) raises UnicodeEncodeError mid-write — and can leave a half-written JSON,
which then makes every subsequent env_read fail with “Read env json error” (caught, but
silently resets state). Fix: open(env_json, 'w', encoding='utf-8') + write to temp + rename.
B7 — Timezone UTC+8 ⇒ “China”: Singapore/Malaysia/W.Australia silently routed to CN infrastructure
Labels: preview-tester ux Severity: Medium (for international users)
tools/cli_command/util.py:260-285: country detection = “is local UTC offset exactly +8h”.
UTC+8 covers Singapore, Malaysia, Philippines, Western Australia, Irkutsk, parts of Siberia…
All of these users get gitee mirrors (set_repo_mirro), aliyun PyPI index
(cli_prepare.py:285-287), and tuyacn CDN downloads — which are slow or blocked for them.
No override flag exists (only undocumented OPEN_COUNTRY_CODE). Add tos.py config region
or respect OPEN_COUNTRY_CODE officially + document it.
B8 — IDE-generated CLI wrappers hardcode the extension’s versioned path; updates orphan every existing project
Labels: preview-tester bug ux Severity: Medium
.tuyaopen/ide/bin/tuyaopen.cmd contains:
node "c:\Users\<user>\.antigravity-ide\extensions\tuyaopen.tuyaopenide-0.1.5\out\cli\cli.js" %*
and devplat-cli-launcher.js:77 pins tuyaopen.tuyaopenide-0.1.5\vendor\.... After the
extension updates to 0.1.6, old projects’ wrappers point to a deleted directory → cryptic
Cannot find module from node until the IDE happens to regenerate them. Consider resolving
via a stable shim path (latest-version symlink/dir) or regenerating wrappers on extension
activation for all known projects.
B9 — Windows ARM64 silently gets x86_64 binaries
Labels: preview-tester feature-request Severity: Low
util_tyutool.get_platform_key() lines 43-45: on Windows the arch is forced to x86_64
regardless of platform.machine(). Snapdragon X / ARM64 laptops run tyutool under emulation;
works today, but native arm64 builds would avoid weird perf/AV issues, and the forced override
hides the mismatch from bug reports.
B10 — compile_commands.json (9 MB+) copied into the SDK root
Labels: preview-tester ux Severity: Low
cli_build.py:251-276 copies compile_commands.json to params["open_root"] (the SDK
root), not the app root — a ~9 MB artifact lands in the SDK install dir after every build.
It happens to be in the SDK .gitignore, but it still bloats backups/AV scans and belongs
next to the app (that’s where clangd needs it).
B11 —
tos.py build silently uses a STALE config: hand-editing app_default.config is ignored — LIVE REPRODUCED
Labels: preview-tester bug Severity: High (silent wrong-firmware builds)
Location
tools/cli_command/cli_config.py:99-101:
if force or not os.path.exists(using_config):
_defconfig(app_default_config, using_config, catalog_kconfig)
using.config is regenerated only when missing. _invalidate_derived_artifacts()
(the only thing that clears derived state) is called from exactly ONE place:
config set (cli_config.py:414). Plain tos.py build never compares mtimes
(zero hits for getmtime/st_mtime in the whole codebase).
Live reproduction (performed on this machine, sandbox project)
- Fresh project with
CONFIG_PROJECT_VERSION="0.1.0"→ run build →
.build/cache/using.configcreated with0.1.0. - Edit
app_default.config→CONFIG_PROJECT_VERSION="0.2.0". - Run
tos.py buildagain.
Result: app_default.config says 0.2.0; using.config STILL says 0.1.0 — and the
entire build pipeline runs with the stale values. Same applies to product ID, board symbols,
component enables… everything. The user “changes config, rebuilds” and flashes firmware that
silently doesn’t contain their change. Only workarounds: tos.py clean (full rebuild) or
re-doing changes via tos.py config set.
Suggested fix
In init_using_config, regenerate when mtime(app_default.config) > mtime(using.config)
(or hash-compare), and call _invalidate_derived_artifacts() when it changes.
B12 —
tos.py dev bac permanently overwrites the project’s app_default.config
Labels: preview-tester bug Severity: High (data loss)
Location
tools/cli_command/cli_dev.py:116-152 (build_all_config_exec):
for idx, config in enumerate(config_list):
copy_file(config, app_default_config) # overwrites YOUR app config
ok = build_project(...)
...
# loop ends — original app_default.config is NEVER restored
dev bac iterates board/app configs and copies each one over app_default.config to drive
the build. After the last iteration, the developer’s hand-tuned default (product ID, board,
feature flags) has been replaced by whichever config sorted last. No backup, no restore, no warning.
Impact
Running the documented multi-config build once silently rewrites project identity
(CONFIG_TUYA_PRODUCT_ID etc.). Next normal build flashes firmware bound to the WRONG product.
Suggested fix
Save the original to memory/temp at start and restore in a finally: block (or pass the
config via -DTOS_* overrides instead of mutating files).
B13 — Systemic crash on non-interactive stdin: 6 unguarded input() calls raise raw EOFError
Labels: preview-tester bug Severity: Medium (breaks CI / AI-agent usage)
Only two call sites guard with sys.stdin.isatty() (util_tyutool.py:285,
cli_config.py:588). Everywhere else, any prompt crashes with an unhandled traceback as soon
as stdin is closed/piped — exactly how CI runners and coding agents invoke CLIs:
| Location | Prompt | Crash trigger |
|---|---|---|
cli_build.py:65 |
platform commit mismatch y/n/d | build with outdated platform + no TTY |
cli_monitor.py:185 |
serial port chooser | monitor with multiple ports + no TTY |
cli_new.py:424,432,475,540 |
new project/board/platform names | scripted project creation |
util.py:243 (list_menu) |
all interactive menus | piped input |
Example failure: tos.py check && tos.py build in CI where the platform repo is one commit
behind → EOFError traceback instead of a clear “platform update required, run X”.
Suggested fix
Central helper ask_choice() that returns a sensible default / fails with a readable error
when not sys.stdin.isatty() (or an explicit --yes/-n flag per command).
B14 — Running tos.py with a non-venv Python dies with a raw ModuleNotFoundError
Labels: preview-tester ux Severity: Low-Medium (first-contact onboarding)
python C:\...\TuyaOpenSDK\tos.py version with system Python (very common first attempt,
since docs say python tos.py …) crashes with:
ModuleNotFoundError: No module named 'git'
No hint that the SDK venv must be activated (export.ps1) or that .venv\Scripts\python.exe
must be used. A one-line try/except around the imports suggesting “run export.ps1 first”
would save every newcomer 20 confused minutes.
Task completion summary (Tasks 1–5)
| Task | Status | Notes |
|---|---|---|
| 1 — Installation | Preview extension installed in VS Code + Antigravity IDE (v2.5.5) | |
| 2 — Project creation | Natural-language project generation used; scaffolded base framework project on t5ai |
|
| 3 — Flashing & HIL validation | tyutool GUI + CLI flows; found Bugs 3/4/5; full HIL loop verified (voice chat, LED, timer, panel control) | |
| 4 — AI Agent integration | Claude Code wired through repo skill files (AGENTS.md/CLAUDE.md); multiple build→flash→monitor→fix loops completed; found Bugs 2/8 |
|
| 5 — Free exploration | Built a complete product: desk double-knock wake detection on raw mic PCM, focus-timer with agent-context injection, Ray panel with style linkages; destructive tests included forced mute-during-knock, reflash-unbind, cloud-timer abuse |
UX / feature-request suggestions (label: feature-request ux)
- Flash-time RST coach-mark (ties to Bug 4) — per-board flashing hints.
- PYTHONUTF8 default (Bug 1) — or ship
sitecustomize.pyin the SDK venv. - Port role naming (Bug 5) — “Flash”/“Log” labels everywhere ports appear.
- Cloud timer DP validation (Bug 2) — platform-side schema check at publish.
- Unbind warning (Bug 3) — pre-flash warning + post-flash re-pair wizard.
- Agent device-state grounding (Bug 8) — standard DP-snapshot injection for agents.
- Timer alerts in alert enum (Bug 7) —
TIMER_START/TIMER_END. - Error-code dictionary (Bug 9) — searchable table of SDK/MQTT error codes in docs.
Submission checklist (per guidelines)
- Environment block (OS / IDE / Preview version) — top of this file
- Hardware model — T5AI-Core
- Reproduction steps — every bug
- Expected vs actual — every bug
- Evidence — attach screenshots/logs listed per bug when posting
- Severity — assigned per bug
- Labels —
preview-testerbug(+ux/feature-requestwhere relevant)


