Full Audit - Bugs
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 19.9k
- Forks
- 2.3k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 30
Description
jcode Codebase Audit
Project: jcode v0.11.4 — AI coding agent harness
Scale: ~344K lines Rust, 731 files
Summary
| Severity | Count |
|---|---|
| Critical | 5 |
| High | 8 |
| Medium | 13 |
| Low | 11 |
| Total | 37 |
Critical
1. Unsound Unsafe Code — Undefined Behavior in Windows Transport
File: src/transport/windows.rs:120–128
pub fn split(&mut self) -> (SplitReadRef<'_>, SplitWriteRef<'_>) {
let ptr = self as *mut Stream;
unsafe {
(
SplitReadRef { stream: &mut *ptr },
SplitWriteRef { stream: &mut *ptr },
)
}
}
Creates two &mut references to the same Stream — undefined behavior per Rust's aliasing rules. If both are used concurrently from different async tasks (which is the entire point), the compiler may misoptimize reads/writes. The codebase uses Arc<Mutex<Stream>> for into_split() elsewhere, but this split() path is unsound.
2. Session Save Failures Silently Discarded
Files: src/tui/app/turn.rs:275, 828, 895, 1165, 1178 + ~30 more across TUI
let _ = self.session.save() silently discards persistence failures. The most critical save (post-turn assistant message at line 828) is swallowed. If disk is full or permissions are wrong, the user loses their entire conversation with zero indication. Line 966 proves the pattern is known (self.session.save()?), making the other five in the same file oversights.
3. Nested std::sync::Mutex Locks in Async Context — Blocks Runtime
File: src/memory/pending.rs:85–123
take_pending_memory acquires three nested std::sync::Mutex locks (PENDING_MEMORY → LAST_INJECTED_PROMPT_SIGNATURE → LAST_INJECTED_MEMORY_SET). These block the OS thread, stalling the tokio async runtime. No defined lock ordering protocol exists across the codebase.
4. Mutex<Agent> Held Across .await Points — Deadlock Risk
Files: src/server/client_state.rs:455–528, debug_command_exec.rs:147, debug_jobs.rs:125, swarm.rs:1004, 1019, 1062
The agent.lock().await guard is held through multiple .await calls (tool_names().await, compaction_mode().await) that acquire internal RwLocks. If any other code path acquires those RwLocks first then tries to lock the Agent, deadlock occurs.
5. 100+ Global Mutable Statics — Prevents Testing, Enables Races
Files: src/tui/, src/agent.rs, src/usage/, src/provider/
The TUI alone has 63 global statics (OnceLock<Mutex<...>>, Atomic*). The codebase has 100+ total. This makes unit testing nearly impossible (hence the 79 #[cfg(test)]/#[cfg(not(test))] alternations in ui.rs), prevents multi-instance usage, and creates subtle race conditions from stale state.
High
6. God Module: src/tui/ui.rs — 2,578 Lines + 29 #[path] Directives
The file aggregates 29 submodules via #[path = "..."] overrides, pulling in ~40,000+ total lines. 12 functions have their entire bodies duplicated for test vs. non-test. The update_prompt_entry_animation function has 130 lines of verbatim duplication.
7. Error Classification via String Matching — Fragile
Files: src/provider/failover.rs:206–273, src/auth/oauth.rs:1030, src/auth/cursor.rs:514
Retry and failover decisions are made by .contains() on lowercased error strings. "billing" matches any error mentioning billing. "quota" matches unrelated errors. If upstream providers change their error messages, failover behavior silently degrades. There are only 2 typed error enums in the entire 344K-line codebase.
8. Operator Precedence Bug in Retryable Error Check
File: src/provider/openrouter_sse_stream.rs:199–210
fn is_retryable_error(error_str: &str) -> bool {
crate::provider::is_transient_transport_error(error_str)
|| error_str.contains("stream error")
|| error_str.contains("eof")
|| error_str.contains("5") // binds with &&, not ||
&& (error_str.contains("50")
|| error_str.contains("502")
|| ...)
|| error_str.contains("overloaded")
}
&& binds tighter than ||, so the contains("5") check only fires conjunctively. Meanwhile contains("502") already implies contains("5"), making the entire clause redundant dead logic.
9. Five Divergent is_retryable_error Implementations
Files: anthropic.rs:1534, openai_stream_runtime.rs:1064, copilot.rs:979, claude.rs:1060, openrouter_sse_stream.rs:199
Each has different coverage. claude.rs is missing 500 Internal Server Error. openai_stream_runtime.rs doesn't delegate to the shared is_transient_transport_error, missing TLS/DNS errors. These should be one function.
10. Unbounded SSE Buffer Growth — Memory Leak
Files: src/provider/anthropic.rs:1466–1509, copilot.rs:834, openrouter_sse_stream.rs:512
The buffer string grows without bound. parse_sse_event only drains up to \n\n — if the server sends malformed data with no double-newline, the buffer grows forever. No size limit or compaction exists. Same pattern for ToolUseAccumulator.input_json which accumulates arbitrarily large tool inputs.
11. Function Takes 22 Parameters
File: src/server/client_lifecycle.rs:137
handle_lightweight_control_request takes 22 parameters. Its callers pass the same 22 arguments in deeply repetitive match arms. Needs a context struct.
12. 350-Line Command Dispatcher
File: src/tui/app/commands.rs:1151–1501
handle_session_command is a 350-line if/else chain handling ~30 slash commands with no separation of concerns.
13. Inconsistent Default Model Fallbacks
File: src/provider/mod.rs:1073–1110
MultiProvider::model() fallback for Claude is "claude-opus-4-5-20251101" but anthropic.rs DEFAULT_MODEL is "claude-opus-4-6". Copilot fallback is "claude-sonnet-4" but copilot.rs uses "claude-sonnet-4-6". These mismatches mean the fallback path produces a different model than the provider's own default.
Medium
14. Dead Duplicate Files (535 lines of dead code)
src/usage_display.rs(176 lines) is byte-identical tosrc/usage/display.rssrc/usage_openai.rs(359 lines) is byte-identical tosrc/usage/openai_helpers.rssrc/tui/color_support.rsis byte-identical tocrates/jcode-tui-workspace/src/color_support.rs
15. 100+ Hardcoded rgb() Magic Numbers
Despite ui_theme.rs defining named color functions (user_color(), file_link_color()), raw rgb(138, 180, 248) literals are scattered across 10+ TUI files. If the theme changes, these won't update.
16. Full-Frame clear_area Defeats Differential Rendering
File: src/tui/ui.rs:1852
clear_area(frame, area); // "needed for macOS" but applied on ALL platforms
Forces a full-screen redraw every frame, defeating ratatui's diff-based rendering on all platforms.
17. Double prepare_messages on Every Frame
File: src/tui/ui.rs:2121–2141
When content overflows, prepare_messages is called twice per frame (full width, then narrow width). This is O(n) over all messages in the session.
18. No Path Traversal Protection
File: src/tool/mod.rs:158–166
resolve_path joins relative paths to working_dir with no canonicalize or traversal check. ../../etc/passwd resolves without complaint.
19. No SSRF Protection in WebFetch
File: src/tool/webfetch.rs:70–73
URL validation checks for http:///https:// prefix but doesn't block internal network addresses (169.254.169.254, localhost, 127.0.0.1). Cloud metadata endpoints are accessible.
20. Unbounded Collections — Memory Growth
src/server/runtime.rs:36—file_touches: HashMap<PathBuf, Vec<FileAccess>>has no evictionsrc/server/runtime.rs:43—event_history: VecDeque<SwarmEvent>grows foreversrc/provider/openrouter.rs:140–156—DISK_CACHE_MEMOgrows monotonicallysrc/session.rs:132—env_snapshots,memory_injections,replay_eventsunbounded within session
21. Copilot fork() Shares Mutable State Inconsistently
File: src/provider/copilot.rs:1128–1144
fork() shares bearer_token, fetched_models, premium_mode, user_turn_count via Arc. Anthropic's fork() creates fresh credentials. A forked Copilot provider modifying premium_mode affects the original.
22. std::sync::Mutex Does File I/O Under Lock
src/memory_log.rs:20, 43–48—writeln!+flushwhile holding the locksrc/telemetry.rs:20— constructs HTTP payloads while holding the lock
Both can block the async runtime thread.
23. Hardcoded Version Strings Masquerading as Claude Code CLI
File: src/provider/anthropic.rs:41–80
const CLAUDE_CLI_USER_AGENT: &str = "claude-cli/2.1.123 (external, sdk-cli)";
const OAUTH_BILLING_HEADER: &str = "cc_version=2.1.123; cc_entrypoint=sdk-cli; cch=33f85;";
Six different version strings scattered across constants with no single source of truth. The billing header hash cch=33f85 will silently become stale.
24. 8+ Re-implementations of EnvVarGuard Test Utility
An identical set/restore env var pattern is independently implemented in 8+ test files. Should be a shared utility.
25. Debug Socket Allows Arbitrary Tool Execution
File: src/server/debug_command_exec.rs:117–617
Any local process can connect and execute arbitrary tool calls, change providers, or send messages. Socket permissions are owner-only, but on a shared machine this is still a risk.
26. Missing Config Validation
File: crates/jcode-config-types/src/lib.rs
CompactionConfig f32 fields (ewma_alpha, proactive_floor, etc.) have no range validation. DisplayConfig::animation_fps is documented as "1–120" but unchecked. AmbientConfig doesn't validate min_interval_minutes < max_interval_minutes.
Low
27. unreachable!() in Wire Format Parser
File: src/provider/cursor.rs:722
External data with an unexpected wire type will panic instead of returning an error.
28. Provider Trait Has 37 Methods — Interface Segregation Violation
File: src/provider/mod.rs:66–360
Methods like premium_mode(), transport(), native_compaction_mode() are OpenAI/Copilot-specific but pollute the core trait.
29. Provider::model() Default Returns "unknown"
File: src/provider/mod.rs:102–104
A sentinel string used in logging and routing. Should be Option<String>.
30. No Tests in 10+ Type Crates
jcode-auth-types, jcode-ambient-types, jcode-background-types, jcode-batch-types, jcode-config-types, jcode-gateway-types, jcode-memory-types, jcode-message-types, jcode-side-panel-types, jcode-usage-types — all have zero tests.
31. 9 Workspace Crates Missing publish = false
Internal crates like jcode-agent-runtime, jcode-embedding, jcode-pdf, etc. could be accidentally published to crates.io.
32. 68 Flat Modules in src/lib.rs
No intermediate grouping. The root crate has 76 public modules and ~50 direct dependencies compiled as one unit.
33. Log Files Created Without Restrictive Permissions
File: src/logging.rs:156–160
Created with default umask (typically 0o644), but logs contain truncated tool inputs/outputs that could include sensitive data.
34. Dummy Waker in Windows Transport
File: src/transport/windows.rs:149–160
A no-op Waker that never wakes. Works for one-shot polling but is fragile.
35. Permission Recording Failures Silently Discarded
File: src/tui/permissions.rs:55, 70, 85, 96
If record_permission_via_file() fails, a user's approve/deny decision is lost with no indication.
36. Git Dependencies
agentgrep and mermaid-rs-renderer are pinned by git tag. Less reliable than registry dependencies.
37. Release Profile Uses opt-level = 1 and codegen-units = 256
File: Cargo.toml:210–214
Unusually low quality for a release build. A separate release-lto profile exists for distribution.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
This is a broad audit rather than a scoped change: start by selecting one finding and reading its cited entry point, such as src/transport/windows.rs, src/provider/openrouter_sse_stream.rs, or src/tool/webfetch.rs. Confirm the behavior with a focused reproduction or existing test, then narrow the issue to one verified bug with a regression test and clear completion criteria.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend, cli, performance, security, testing-qa
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 15/100