Skip to content

feat(ipc): add Unix domain socket for inter-process communication#10

Merged
wilcorrea merged 3 commits into
mainfrom
feat/unix-socket-ipc
Feb 23, 2026
Merged

feat(ipc): add Unix domain socket for inter-process communication#10
wilcorrea merged 3 commits into
mainfrom
feat/unix-socket-ipc

Conversation

@wilcorrea

@wilcorrea wilcorrea commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Implements Unix domain socket server in ~/.arandu/arandu.sock for fast IPC communication
  • Adds three commands: open (opens markdown files), ping (health check), show (brings window to focus)
  • Updates CLI tool to prefer socket communication with automatic fallback to open --args when app isn't running
  • Socket uses JSON line-delimited protocol with proper error handling and 0600 permissions

Test plan

  • Start Arandu app and verify socket is created at ~/.arandu/arandu.sock
  • Test ping command: echo '{"command":"ping"}' | nc -U ~/.arandu/arandu.sock
  • Test opening a file: echo '{"command":"open","path":"/path/to/file.md"}' | nc -U ~/.arandu/arandu.sock
  • Test show command: echo '{"command":"show"}' | nc -U ~/.arandu/arandu.sock
  • Verify CLI tool works: arandu path/to/file.md (should use socket if app is running)
  • Test fallback: close app and run arandu file.md (should start app using traditional method)
  • Verify stale socket cleanup: create fake socket file, start app, confirm it's cleaned up
  • Test error handling: send invalid JSON, invalid command, missing path field

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Unix-domain IPC so command-line requests can talk to a running instance — opening files/paths now reuses existing windows and can show/focus the app.
    • Installer fast-path: CLI tries the IPC socket first for quicker open/show actions, falling back to launching the app if needed.
  • Chores

    • Updated runtime dependency to enable additional IO/network capabilities.

- Create ipc.rs module with async socket server
- Implement commands: open, ping, show
- Socket path: ~/.arandu/arandu.sock with 0600 permissions
- Update CLI script to use socket with fallback to open
- Add tokio features: net, io-util
- Graceful degradation if socket setup fails
@wilcorrea

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Feb 23, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Feb 23, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds Unix-domain socket IPC at ~/.arandu/arandu.sock with JSON commands ("open", "ping", "show") and a CLI fast-path; integrates socket setup/cleanup into the Tauri app lifecycle (Unix-only) and enables additional Tokio features (net, io-util) in Cargo.toml.

Changes

Cohort / File(s) Summary
Dependency
apps/tauri/src-tauri/Cargo.toml
Enabled additional Tokio features: added net and io-util to existing fs feature.
IPC Module
apps/tauri/src-tauri/src/ipc.rs
New Unix-domain socket server at ~/.arandu/arandu.sock: creation/cleanup, permission handling, per-connection tasks, newline-delimited JSON commands/responses; implements open, ping, show handling and exports SocketState, setup, cleanup.
App Integration
apps/tauri/src-tauri/src/lib.rs
Registers ipc module under #[cfg(unix)], adds SocketState to app state, invokes ipc::setup during initialization and ipc::cleanup on shutdown (Unix-only).
CLI Installer Script
apps/tauri/src-tauri/src/cli_installer.rs
Embedded Bash script fast-path: connects to socket, sends JSON show or open commands with absolute paths when available; falls back to locating/launching Arandu.app if IPC unavailable or any IPC step fails.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI Process
    participant Socket as Unix Socket\n(~/.arandu/arandu.sock)
    participant Server as IPC Server Task
    participant App as Tauri App
    participant Window as Main Window

    CLI->>Socket: connect()
    CLI->>Server: send JSON {"command":"open","path":"/abs/path"}\n
    Server->>Server: parse JSON, canonicalize path
    Server->>App: emit "open-file" event (abs path)
    App->>Window: show / unminimize / focus
    App-->>Server: event handled
    Server->>Socket: send JSON {"success":true}\n
    CLI->>CLI: exit(0)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I found a socket cozy in the ground,
JSON whispers travel soft and round.
"Open" hops in with a tidy map,
The window wakes — no waiting gap.
A rabbit cheers: the app answered back! 🎋

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(ipc): add Unix domain socket for inter-process communication' directly and clearly summarizes the main change: implementing Unix domain socket IPC. All modified files (Cargo.toml, cli_installer.rs, ipc.rs, lib.rs) are centered on this feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/unix-socket-ipc

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/tauri/src-tauri/src/cli_installer.rs`:
- Around line 8-17: The IPC block around SOCKET currently assumes nc always
succeeds, skips sending when no args are passed, and embeds raw paths into JSON;
change it so you first detect socket existence, then if $# -eq 0 send a single
'{"command":"open"}' over the socket, otherwise iterate over for f in "$@" and
build ABS as now but escape backslashes and double quotes in "$ABS" (e.g.
replace \ with \\ and " with \") before embedding into the JSON payload, write
the payload to nc and check nc's exit status for each send; if any nc call fails
(non-zero) or the socket is unreachable, stop using the socket and fall back to
the non-IPC code path (i.e. remove the unconditional exit 0 and only exit when
all IPC sends succeeded). Ensure you update references in this block: SOCKET,
the for f in "$@" loop, ABS, the JSON echo/pipe to nc and the exit 0 behavior.

In `@apps/tauri/src-tauri/src/ipc.rs`:
- Around line 24-53: The current setup() sets SocketState before attempting
UnixListener::bind, which can cause cleanup to remove another instance's socket
if bind fails; change the flow so you call socket_path() and
cleanup_stale_socket(&sock_path) as before, then attempt
UnixListener::bind(&sock_path) first, and only after bind returns Ok(listener)
acquire the state mutex (state.0.lock()) and set *guard =
Some(sock_path.clone()) before spawning the async task that calls
socket_listener_loop; on Err(e) do not set SocketState and handle/log/propagate
the error instead.

In `@apps/tauri/src-tauri/src/lib.rs`:
- Line 376: The code unconditionally registers
ipc::SocketState(Mutex::new(None)) with .manage(...) but ipc::SocketState is
only available on Unix builds; wrap the manage call in a cfg guard so it only
compiles on Unix (e.g., use #[cfg(unix)] or cfg_if! around the .manage(...)
invocation) or provide a non-Unix stub type and conditional module export;
target the .manage call that references ipc::SocketState and the ipc module to
make the registration conditional.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05f835a and 3a67e1f.

📒 Files selected for processing (4)
  • apps/tauri/src-tauri/Cargo.toml
  • apps/tauri/src-tauri/src/cli_installer.rs
  • apps/tauri/src-tauri/src/ipc.rs
  • apps/tauri/src-tauri/src/lib.rs

Comment thread apps/tauri/src-tauri/src/cli_installer.rs
Comment thread apps/tauri/src-tauri/src/ipc.rs
Comment thread apps/tauri/src-tauri/src/lib.rs Outdated
…cfg guard

- CLI script: handle empty args (send "show"), escape paths for JSON,
  check nc exit status and fall back on failure
- ipc.rs: set SocketState only after successful UnixListener::bind
- lib.rs: wrap ipc::SocketState manage call with #[cfg(unix)]

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/tauri/src-tauri/src/ipc.rs`:
- Around line 63-71: The ~/.arandu directory creation in socket_path does not
enforce restrictive permissions; after creating the directory with
std::fs::create_dir_all(&arandu_dir) update the code to explicitly set
permissions to 0o700 (owner rwx only) to prevent cross‑user access. Use
std::fs::set_permissions(&arandu_dir, std::fs::Permissions::from_mode(0o700))
(with std::os::unix::fs::PermissionsExt imported) and propagate/map any error
into the same Result error path so failures to apply the 0700 permissions are
handled and logged similarly to the existing create_dir_all error. Ensure this
change is applied in the socket_path function surrounding the arandu_dir
variable.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a67e1f and 81701c5.

📒 Files selected for processing (3)
  • apps/tauri/src-tauri/src/cli_installer.rs
  • apps/tauri/src-tauri/src/ipc.rs
  • apps/tauri/src-tauri/src/lib.rs

Comment thread apps/tauri/src-tauri/src/ipc.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/tauri/src-tauri/src/ipc.rs (1)

94-110: Add a small backoff on accept errors to avoid tight loops.

If the listener starts erroring (e.g., after shutdown), the loop can spin and log rapidly; a short delay prevents that. If Tokio’s time feature isn’t enabled yet, you’ll need to add it.

♻️ Proposed backoff on accept errors
-use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
+use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
+use tokio::time::sleep;
+use std::time::Duration;
             Err(e) => {
                 eprintln!("Accept error: {}", e);
+                sleep(Duration::from_millis(200)).await;
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/tauri/src-tauri/src/ipc.rs` around lines 94 - 110, The accept error
handling in socket_listener_loop currently immediately continues on Err(e),
which can cause a tight loop; modify the Err branch in socket_listener_loop to
await a short sleep (e.g., tokio::time::sleep(Duration::from_millis(100)).await)
after logging the error to provide a small backoff, and ensure the tokio "time"
feature is enabled in Cargo.toml if not already; keep using the same
listener.accept().await flow and spawn behavior for Ok((stream, _addr)).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/tauri/src-tauri/src/ipc.rs`:
- Around line 94-110: The accept error handling in socket_listener_loop
currently immediately continues on Err(e), which can cause a tight loop; modify
the Err branch in socket_listener_loop to await a short sleep (e.g.,
tokio::time::sleep(Duration::from_millis(100)).await) after logging the error to
provide a small backoff, and ensure the tokio "time" feature is enabled in
Cargo.toml if not already; keep using the same listener.accept().await flow and
spawn behavior for Ok((stream, _addr)).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81701c5 and 3e206b7.

📒 Files selected for processing (1)
  • apps/tauri/src-tauri/src/ipc.rs

@wilcorrea wilcorrea merged commit 51408db into main Feb 23, 2026
1 check passed
@wilcorrea wilcorrea deleted the feat/unix-socket-ipc branch February 23, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant