release: deepseek-tui 0.4.7 + Devin .env empty-key fix#19
Conversation
Bump workspace version 0.4.6 -> 0.4.7 and ship the bug fix flagged by Devin on PR #18: an uncommented `DEEPSEEK_API_KEY=` line in .env.example caused `cp .env.example .env` to load an empty key, which `apply_env_overrides` then propagated into the config and `Config::validate()` rejected on startup with "api_key cannot be empty string". - .env.example: comment out the empty `DEEPSEEK_API_KEY=` placeholder. - crates/tui/src/config.rs: skip empty `DEEPSEEK_API_KEY` env values in `apply_env_overrides`, matching the facade's empty-string filter. - Add `apply_env_overrides_ignores_empty_api_key` regression test. - Bump Cargo.toml workspace version, npm wrapper version + binary version, and Cargo.lock. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the project version to 0.4.7 and modifies the configuration logic to ignore empty 'DEEPSEEK_API_KEY' environment variables. Review feedback recommends trimming the API key before it is stored to prevent authentication failures from accidental whitespace. Furthermore, it is suggested to use the 'tempfile' crate in the added test case for safer and more idiomatic temporary directory handling.
| if let Ok(value) = std::env::var("DEEPSEEK_API_KEY") | ||
| && !value.trim().is_empty() | ||
| { | ||
| config.api_key = Some(value); | ||
| } |
There was a problem hiding this comment.
It's recommended to trim the API key before storing it in the configuration. Environment variables, especially those loaded from .env files, can often contain accidental leading or trailing whitespace which might cause authentication issues with the API provider.
| if let Ok(value) = std::env::var("DEEPSEEK_API_KEY") | |
| && !value.trim().is_empty() | |
| { | |
| config.api_key = Some(value); | |
| } | |
| if let Ok(value) = std::env::var("DEEPSEEK_API_KEY") | |
| && !value.trim().is_empty() | |
| { | |
| config.api_key = Some(value.trim().to_string()); | |
| } |
| let nanos = SystemTime::now() | ||
| .duration_since(UNIX_EPOCH) | ||
| .unwrap() | ||
| .as_nanos(); | ||
| let temp_root = env::temp_dir().join(format!( | ||
| "deepseek-tui-empty-key-{}-{}", | ||
| std::process::id(), | ||
| nanos | ||
| )); | ||
| fs::create_dir_all(&temp_root)?; | ||
| let _guard = EnvGuard::new(&temp_root); |
There was a problem hiding this comment.
The test manually constructs a temporary directory path and creates it without ensuring cleanup. Using tempfile::tempdir() is a safer and more idiomatic approach in Rust; it automatically handles unique naming and deletes the directory when the returned TempDir object is dropped, preventing temporary file accumulation.
| let nanos = SystemTime::now() | |
| .duration_since(UNIX_EPOCH) | |
| .unwrap() | |
| .as_nanos(); | |
| let temp_root = env::temp_dir().join(format!( | |
| "deepseek-tui-empty-key-{}-{}", | |
| std::process::id(), | |
| nanos | |
| )); | |
| fs::create_dir_all(&temp_root)?; | |
| let _guard = EnvGuard::new(&temp_root); | |
| let temp_dir = tempfile::tempdir()?; | |
| let _guard = EnvGuard::new(temp_dir.path()); |
release: deepseek-tui 0.4.7 + Devin .env empty-key fix
Security/infra (deep-dive #1-#5, #14-#15, #19-#21, #25, #33-#34, #36-#37, #42): - require bearer token on /v1/chat/completions; real 4xx/5xx statuses - request body (16 MiB) and SSE frame bounds; graceful shutdown on SIGTERM/ctrl-c; constant-time token compare; stdio config get redacts secrets; stdio shutdown reaps the runtime child; RuntimeBridge::drop no longer blocks the runtime thread - user-layer ExecPolicy rules outrank agent-layer; chained commands no longer propose trusted-prefix amendments - atomic config save with one-time backup on all platforms; atomic secrets writes with fsync; empty ProviderChain no longer panics Core/state/mcp (deep-dive #10-#13, #16-#18): - paused jobs persist as Paused across restarts; unarchive refreshes the in-memory cache; tool dispatch timeout with error frame; MCP notifications get no JSON-RPC response; checkpoint parse errors propagate instead of loading empty state; session index compacts at threshold; thread-goal usage recording no longer self-deadlocks the state store Also adds Zai GLM-5-Turbo and OpenRouter z-ai/glm-5.2 to the bundled catalog (picker/router drift).
Summary
0.4.6→0.4.7so the release workflow can ship a fresh tag (the priorv0.4.6release run failed at the parity gate; that tag is intentionally left in place)..env.example/apply_env_overridesbug Devin flagged on PR #18.Bug fix
A new user following
setup --statusguidance (cp .env.example .env) would hit a startup crash:.env.example:7had an uncommentedDEEPSEEK_API_KEY=placeholder.dotenvy::dotenv()loaded the empty value into the process env.apply_env_overrides(crates/tui/src/config.rs) unconditionally setconfig.api_key = Some("").Config::validate()then bailed withapi_key cannot be empty string.The facade crate (
crates/config/src/lib.rs:557-559) already filters empty values; the TUI's override path did not. This PR aligns the two and comments out the placeholder.Changes
.env.example:DEEPSEEK_API_KEY=→# DEEPSEEK_API_KEY=.crates/tui/src/config.rs::apply_env_overrides: only overrideconfig.api_keywhen the env value is non-empty after trim.apply_env_overrides_ignores_empty_api_key.Cargo.toml,Cargo.lock,npm/deepseek-tui/package.json(version+deepseekBinaryVersion) bumped to0.4.7.Test plan
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --all-features --locked -- -D warningscargo test --workspace --all-features --locked— 781 passed (one new regression test)v0.4.7tag (parity broke previously on fmt; main HEAD is fmt-clean since Verify app smoke and add NIM env support #18).After merge, push tag
v0.4.7to trigger the release workflow which builds platform binaries, creates a GH release, and publishes the npm wrapper via OIDC Trusted Publishing.