Skip to content

Prevent monitor status TCC prompts in Git-Same 3.2.0#21

Open
manuelgruber wants to merge 10 commits into
mainfrom
C/TCC
Open

Prevent monitor status TCC prompts in Git-Same 3.2.0#21
manuelgruber wants to merge 10 commits into
mainfrom
C/TCC

Conversation

@manuelgruber

@manuelgruber manuelgruber commented Jul 18, 2026

Copy link
Copy Markdown
Member

This adds host-facing atomic status mirroring through IpcConfig, migrates legacy status symlinks safely, and keeps mirror failures non-fatal while primary writes still fail normally, preventing macOS cross-app access prompts in the Tauri host.

It adds a backward-compatible optional monitor_version field to FinderStatus, reports app/monitor build skew, and automatically restarts an already-installed stale monitor after upgrades so status recovers without manual intervention.

It also bumps Git-Same to 3.2.0 and refreshes in-range Cargo and pnpm dependencies.

Validation passed with cargo fmt --all -- --check, cargo test --workspace, and pnpm --dir crates/git-same-app/ui check.

Summary by Sourcery

Add host-facing status mirroring and monitor version tracking to avoid macOS TCC prompts and handle monitor/app skew while bumping Git-Same to 3.2.0.

New Features:

  • Introduce host-facing status mirroring via IpcConfig so the monitor writes status.json both to the app-group container and to the host config directory.
  • Expose a host-specific IPC configuration (HostIpc) and use it across app commands and status streaming to read status from the host directory instead of the app-group container.
  • Add an optional monitor_version field to FinderStatus and surface monitor/app build skew in requirement checks and UI types.

Bug Fixes:

  • Avoid macOS TCC prompts by preventing the non-sandboxed Tauri host from following status.json symlinks into the app-group container and by mirroring a real file into the host directory.
  • Treat corrupt or missing status.json files as stale status snapshots rather than hard errors, and ensure legacy status symlinks are safely removed or replaced.
  • Ensure monitor status writes remain atomic while allowing mirror write failures to be non-fatal.

Enhancements:

  • Refine legacy symlink handling so only the socket is symlinked and status.json is mirrored as a real file, with tests for migration behavior.
  • Refactor status writer construction through IpcConfig::status_writer to encapsulate platform-specific mirroring, and use a shared StatusFileWriter in the monitor socket handler.
  • Improve requirement messaging with better guidance for stale status and version skew, and add upgrade-time auto-restart of an already-installed monitor when legacy status symlinks are detected.
  • Filter filesystem watch events to only react to final status.json changes and avoid duplicate status-updated emissions.

Build:

  • Cap the time crate below 0.3.52 to work around an upstream cookie/time compatibility issue.
  • Bump workspace, app UI, and Badges extension versions to 3.2.0 and refresh selected JS dependencies (e.g., @lucide/svelte, @tauri-apps/cli, vite).

Documentation:

  • Document the IPC layout, host-facing status path, and rationale for avoiding cross-app container access in comments across IPC, commands, and status streaming modules.

Tests:

  • Add tests for status mirroring, symlink removal, legacy symlink behavior, corrupt status handling, and host/legacy IPC path consistency.
  • Extend monitor requirement tests to cover monitor_version stamping and version skew reporting.
  • Add tests for status watcher event filtering to distinguish status.json from its temporary files.

Summary by CodeRabbit

  • New Features
    • Added monitor version reporting to detect app and monitor version mismatches.
    • Improved macOS status synchronization through a host-facing status file mirror.
  • Bug Fixes
    • Improved recovery from legacy status-file symlinks and invalid status data.
    • Reduced unnecessary status updates by filtering irrelevant file events.
  • Release
    • Updated the application and workspace version to 3.2.0.

The non-sandboxed Tauri host read status.json from the App Group
container, so macOS fired "Git-Same would like to access data from
other apps" up to five times on launch and after each sync.

The monitor now mirrors a real status.json into ~/.config/git-same/
finder/ (StatusFileWriter::new_with_mirrors) in addition to the
container, and the host reads only that host-home copy via a shared
tauri::State<HostIpc> resolved once at startup. ensure_legacy_symlinks
no longer symlinks status.json (only finder.sock), and the host unlinks
any leftover status.json symlink before reading so it never follows a
link into the container during the upgrade window.

The FinderSync extension, the socket, and all entitlements are
unchanged, so no Apple re-sign or macOS-26 re-test is required.
Picks up patch-level updates cargo resolved: anyhow 1.0.103,
aws-lc-rs 1.17.1, aws-lc-sys 0.42.0, camino 1.2.4, among others.
time 0.3.52 changed its sealed Parsable::parse trait method from one argument to two (added defaults: Option<Parsed>). cookie 0.18.1, pulled in transitively via tauri, calls the one-argument form and fails to compile against time >= 0.3.52. No fixed cookie release exists (0.18.1 is the latest and tauri pins cookie 0.18), so cap time below 0.3.52 in the git-same-app manifest and re-pin the lockfile to the latest compatible time 0.3.51. Remove the cap once cookie ships a fix.
Updates 6 crates.io packages (clap_complete, console, indicatif,
inotify-sys, libredox, tauri) and 3 npm packages (@lucide/svelte,
@tauri-apps/cli, vite) to their latest semver-compatible releases.
The time <0.3.52 cap in git-same-app/Cargo.toml stays in place since
tauri's transitive cookie 0.18.1 still hasn't shipped a fix.
Address findings from an xhigh code review of the status-mirror change:

- Gate the Windows-hostile suffix assert in the IPC test so S1 CI passes.
- Make status mirror writes best-effort (warn, not error) so an unwritable
  host dir cannot crash-loop the monitor under launchd.
- Add core remove_symlink_if_present (NotFound-tolerant), reuse it from the
  host, and drop the duplicated app-side helper and its synthetic test.
- Move the mirror policy into IpcConfig::status_writer so a custom IpcConfig
  can never clobber the real user's host status.json.
- Drop the false state-guard clones in the Tauri handlers, single-parse the
  status snapshot, and filter watcher events to status.json.
- Correct the ipc module docs to describe the mirror design.
The stale-status requirement suggestion said only to restart or wait,
which never resolves an app/monitor version skew (a newer app reading an
older monitor's status). Mention that a restart picks up the new monitor
build so upgraders are not stuck waiting for a scan that cannot help.
Add monitor_version to FinderStatus, stamped in FinderStatus::new with the
building crate's CARGO_PKG_VERSION, so each status records the monitor build
that wrote it. The Tauri Monitor requirement check compares it against the
app's own version and, when a readable status reports a different build,
tells the user to restart the monitor. Informational only: it does not flip
the check to failed, and the stale hint still takes priority when no status
is readable. Old status files without the field parse as None.
After an app upgrade the previously installed monitor keeps running the old
build (launchd KeepAlive keeps the process alive; nothing restarts it). The
old monitor only symlinks the host status.json into the container and never
writes the mirror the new host reads, so the host deletes the leftover
symlink and then shows stale/absent status until a manual restart.

On startup, detect that leftover symlink (a reliable signal an old monitor is
running) via symlink_metadata, which does not follow the link into the
app-group container, and best-effort restart the installed monitor on a
background thread so the on-disk build takes over and starts mirroring. Skip
when no LaunchAgent is installed so a monitor is never created implicitly.
This complements the stale-status guidance text by making the common upgrade
case self-heal without user action.
@cursor

cursor Bot commented Jul 18, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Version 3.2.0 adds host-facing status mirroring, monitor version metadata, IPC configuration injection, filtered status watching, symlink migration handling, and monitor/app version-skew detection.

Changes

Host IPC and monitor synchronization

Layer / File(s) Summary
Release and compatibility contracts
Cargo.toml, crates/git-same-app/Cargo.toml, crates/git-same-app/tauri.conf.json, crates/git-same-app/ui/package.json, crates/git-same-cli/Cargo.toml, macos/GitSameBadges/Info.plist
Package versions are updated to 3.2.0, frontend tooling versions are bumped, CLI alignment is updated, and the time crate is constrained below 0.3.52.
Monitor version status contract
crates/git-same-core/src/types/finder_status.rs, crates/git-same-core/src/types/finder_status_tests.rs, crates/git-same-app/ui/src/lib/types.ts
FinderStatus records an optional monitor version, preserves compatibility with older JSON, and exposes the field to the UI.
Host status mirroring and symlink migration
crates/git-same-core/src/ipc/*
IpcConfig resolves the host status path, StatusFileWriter writes primary and mirror files atomically, and macOS migration retains only the finder.sock symlink.
Monitor writer propagation
crates/git-same-core/src/monitor/*
The monitor passes a configured StatusFileWriter through socket handlers for status writes and reads.
App host IPC and status watcher
crates/git-same-app/src/main.rs, crates/git-same-app/src/commands.rs, crates/git-same-app/src/status_stream.rs, crates/git-same-app/src/status_stream_tests.rs
Tauri stores resolved host IPC state, conditionally restarts an installed monitor after legacy symlink detection, and filters watcher events to relevant status files.
Requirement checks and snapshot recovery
crates/git-same-app/src/commands.rs, crates/git-same-app/src/commands_tests.rs
Requirement checks detect monitor/app version skew, while snapshot reads remove legacy symlinks and handle invalid JSON as stale absent status.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Monitor
  participant StatusFileWriter
  participant HostStatusPath
  participant StatusWatcher
  participant TauriApp
  Monitor->>StatusFileWriter: write FinderStatus
  StatusFileWriter->>HostStatusPath: atomically write status.json
  HostStatusPath->>StatusWatcher: notify status.json event
  StatusWatcher->>TauriApp: emit status-updated snapshot
  TauriApp->>HostStatusPath: read status snapshot
Loading

Possibly related PRs

  • ZAAI-com/git-same#16: Earlier macOS app, Finder badge, and monitor IPC/status plumbing connected to these host IPC and monitor version updates.

Poem

A rabbit hops where status files glow,
Mirrors bloom in paths below.
Monitor versions line up neat,
Symlinks vanish, writes are sweet.
“Three-point-two!” the bunny sings.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: preventing macOS status-related prompts while updating Git-Same to 3.2.0.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch C/TCC

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@sourcery-ai

sourcery-ai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds host-facing status mirroring and monitor version reporting to keep the macOS app and monitor in sync without triggering TCC prompts, refactors IPC/status handling to be safer and more robust in the presence of legacy symlinks and corrupt files, wires the Tauri host to read from the mirrored host path, and bumps the project to the 3.2.0 release line with dependency and test updates.

File-Level Changes

Change Details Files
Introduce atomic primary+mirror status writing and safe legacy symlink handling in the IPC layer.
  • Extend StatusFileWriter to support optional mirror paths and delegate atomic writes to a shared write_atomic helper that overwrites any existing symlink with a real file.
  • Treat mirror write failures as non-fatal warnings while keeping primary-path failures hard errors.
  • Add remove_symlink_if_present utility to safely unlink legacy status.json symlinks without following them, and add targeted tests for mirror behavior and symlink migration.
  • Refine macOS ensure_legacy_symlinks so only finder.sock is symlinked and expose ensure_legacy_symlinks_in for testability, with tests ensuring status.json is no longer symlinked.
crates/git-same-core/src/ipc/status_file.rs
crates/git-same-core/src/ipc/status_file_tests.rs
Route the Tauri host and status watcher through a host-facing IPC config and improve monitor lifecycle handling.
  • Introduce HostIpc state in the app to resolve IpcConfig::host_status_path once at setup and inject it into Tauri commands instead of recomputing default_path.
  • Update check_requirements, read_status, start_sync, and app_requirement_checks to use the injected HostIpc/IpcConfig and to incorporate monitor version skew into requirement messaging and suggestions.
  • Change read_status_snapshot_with to remove any legacy status.json symlink, treat missing/corrupt status as stale snapshots instead of errors, and simplify liveness/staleness computation to a single read.
  • Add restart_monitor_if_installed and call it on startup when a leftover host status symlink is detected, restarting only already-installed monitors to pick up new builds without implicit installation.
crates/git-same-app/src/commands.rs
crates/git-same-app/src/main.rs
crates/git-same-app/src/commands_tests.rs
Make the status watcher host-path aware and reduce redundant snapshot reads from temporary files.
  • Change spawn_watcher to accept an IpcConfig (host-facing) instead of resolving default_path internally, and use read_status_snapshot_with(ipc) for snapshot emission.
  • Introduce event_touches_status_file to filter notify events so only status.json (and pathless rescan events) trigger snapshot reads, skipping status.json.tmp noise.
  • Add unit tests for event_touches_status_file to verify filtering of temp files, final files, mixed-path events, and pathless events.
crates/git-same-app/src/status_stream.rs
crates/git-same-app/src/status_stream_tests.rs
Expose host-facing IPC configuration and status mirroring policy via IpcConfig, and adapt the monitor to reuse a shared StatusFileWriter.
  • Export remove_symlink_if_present from the ipc module and add IpcConfig::host_status_path as the canonical host-facing dir, ensuring it matches legacy_default_path.
  • Add IpcConfig::status_writer that, on macOS when pointing at the app-group container, returns a StatusFileWriter that also mirrors into the host dir; custom dirs and non-macOS remain mirrorless.
  • Update monitor run loop and socket handler to construct a single StatusFileWriter from IpcConfig::status_writer and pass/cloned it into connection handlers, updating status_response to use the writer instead of raw paths.
  • Add tests ensuring host_status_path equals legacy_default_path and that status_writer mirrors only for the group container, with no mirrors for custom dirs.
crates/git-same-core/src/ipc/mod.rs
crates/git-same-core/src/ipc/mod_tests.rs
crates/git-same-core/src/monitor/run.rs
crates/git-same-core/src/monitor/socket_handler.rs
crates/git-same-core/src/monitor/socket_handler_tests.rs
Add monitor build version stamping to FinderStatus and surface app/monitor version skew in checks and UI types.
  • Extend FinderStatus with an optional monitor_version field stamped from env!("CARGO_PKG_VERSION") in FinderStatus::new, and ensure legacy files without the field deserialize with None.
  • Update TypeScript FinderStatus interface to include optional monitor_version to keep the UI schema in sync.
  • Add monitor_version_mismatch helper and pass app_version into monitor_requirement_message/suggestion so they can flag and message version skew when the monitor reports a different build.
  • Add tests for monitor_version stamping/legacy deserialization and for the new version-skew requirement messaging behaviors.
crates/git-same-core/src/types/finder_status.rs
crates/git-same-core/src/types/finder_status_tests.rs
crates/git-same-app/ui/src/lib/types.ts
crates/git-same-app/src/commands_tests.rs
Update versions and build tooling for the 3.2.0 release and pin time to avoid a tauri-cookie incompatibility.
  • Bump workspace, app UI, CLI, and macOS extension versions from 3.1.0 to 3.2.0 and align git-same-cli's git-same-core dependency version constraint.
  • Refresh selected frontend dependencies in the app UI package.json (lucide, tauri CLI, vite) and update the pnpm lockfile accordingly.
  • Add an explicit time = ">=0.3, <0.3.52" dependency to git-same-app with a comment explaining the tauri/cookie incompatibility and intent to remove once fixed.
Cargo.toml
crates/git-same-cli/Cargo.toml
crates/git-same-app/ui/package.json
crates/git-same-app/ui/pnpm-lock.yaml
macos/GitSameBadges/Info.plist
crates/git-same-app/Cargo.toml
crates/git-same-app/tauri.conf.json
Cargo.lock

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@manuelgruber
manuelgruber marked this pull request as ready for review July 18, 2026 08:06
Copilot AI review requested due to automatic review settings July 18, 2026 08:06

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sourcery-ai sourcery-ai 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.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/git-same-app/src/commands.rs`:
- Around line 1013-1026: Update the Monitor requirement’s passed condition in
the checks construction to also require monitor_version_mismatch to be false.
Preserve the existing running-agent and fresh-snapshot checks, while ensuring a
running, fresh monitor with a mismatched version is marked as failed.

In `@crates/git-same-core/src/ipc/status_file.rs`:
- Around line 117-129: Update remove_symlink_if_present to distinguish
symlink_metadata errors: return Ok(false) only for ErrorKind::NotFound, and
propagate all other inspection errors as AppError::path failures. Preserve the
existing symlink removal behavior and error handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cbc0eccc-5949-45b2-81ab-179756176bec

📥 Commits

Reviewing files that changed from the base of the PR and between bab0c0b and 9edcf79.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • crates/git-same-app/ui/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • Cargo.toml
  • crates/git-same-app/Cargo.toml
  • crates/git-same-app/src/commands.rs
  • crates/git-same-app/src/commands_tests.rs
  • crates/git-same-app/src/main.rs
  • crates/git-same-app/src/status_stream.rs
  • crates/git-same-app/src/status_stream_tests.rs
  • crates/git-same-app/tauri.conf.json
  • crates/git-same-app/ui/package.json
  • crates/git-same-app/ui/src/lib/types.ts
  • crates/git-same-cli/Cargo.toml
  • crates/git-same-core/src/ipc/mod.rs
  • crates/git-same-core/src/ipc/mod_tests.rs
  • crates/git-same-core/src/ipc/status_file.rs
  • crates/git-same-core/src/ipc/status_file_tests.rs
  • crates/git-same-core/src/monitor/run.rs
  • crates/git-same-core/src/monitor/socket_handler.rs
  • crates/git-same-core/src/monitor/socket_handler_tests.rs
  • crates/git-same-core/src/types/finder_status.rs
  • crates/git-same-core/src/types/finder_status_tests.rs
  • macos/GitSameBadges/Info.plist

Comment on lines 1013 to +1026
checks.push(RequirementCheckDto {
name: "Monitor".to_string(),
passed: monitor_agent.as_ref().is_some_and(|agent| agent.running)
&& snapshot.as_ref().is_some_and(|snapshot| !snapshot.stale),
message: monitor_requirement_message(monitor_agent.as_ref(), snapshot.as_ref()),
suggestion: monitor_requirement_suggestion(monitor_agent.as_ref(), snapshot.as_ref()),
message: monitor_requirement_message(
monitor_agent.as_ref(),
snapshot.as_ref(),
env!("CARGO_PKG_VERSION"),
),
suggestion: monitor_requirement_suggestion(
monitor_agent.as_ref(),
snapshot.as_ref(),
env!("CARGO_PKG_VERSION"),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mark monitor version skew as a failed requirement.

A running, fresh but mismatched monitor currently sets passed: true, contradicting the restart message and suggestion. Include monitor_version_mismatch in the pass condition.

Proposed fix
         passed: monitor_agent.as_ref().is_some_and(|agent| agent.running)
-            && snapshot.as_ref().is_some_and(|snapshot| !snapshot.stale),
+            && snapshot.as_ref().is_some_and(|snapshot| !snapshot.stale)
+            && monitor_version_mismatch(
+                snapshot.as_ref(),
+                env!("CARGO_PKG_VERSION"),
+            )
+            .is_none(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
checks.push(RequirementCheckDto {
name: "Monitor".to_string(),
passed: monitor_agent.as_ref().is_some_and(|agent| agent.running)
&& snapshot.as_ref().is_some_and(|snapshot| !snapshot.stale),
message: monitor_requirement_message(monitor_agent.as_ref(), snapshot.as_ref()),
suggestion: monitor_requirement_suggestion(monitor_agent.as_ref(), snapshot.as_ref()),
message: monitor_requirement_message(
monitor_agent.as_ref(),
snapshot.as_ref(),
env!("CARGO_PKG_VERSION"),
),
suggestion: monitor_requirement_suggestion(
monitor_agent.as_ref(),
snapshot.as_ref(),
env!("CARGO_PKG_VERSION"),
),
checks.push(RequirementCheckDto {
name: "Monitor".to_string(),
passed: monitor_agent.as_ref().is_some_and(|agent| agent.running)
&& snapshot.as_ref().is_some_and(|snapshot| !snapshot.stale)
&& monitor_version_mismatch(
snapshot.as_ref(),
env!("CARGO_PKG_VERSION"),
)
.is_none(),
message: monitor_requirement_message(
monitor_agent.as_ref(),
snapshot.as_ref(),
env!("CARGO_PKG_VERSION"),
),
suggestion: monitor_requirement_suggestion(
monitor_agent.as_ref(),
snapshot.as_ref(),
env!("CARGO_PKG_VERSION"),
),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/git-same-app/src/commands.rs` around lines 1013 - 1026, Update the
Monitor requirement’s passed condition in the checks construction to also
require monitor_version_mismatch to be false. Preserve the existing
running-agent and fresh-snapshot checks, while ensuring a running, fresh monitor
with a mismatched version is marked as failed.

Comment on lines +117 to +129
pub fn remove_symlink_if_present(path: &Path) -> Result<bool, AppError> {
match std::fs::symlink_metadata(path) {
Ok(meta) if meta.file_type().is_symlink() => match std::fs::remove_file(path) {
Ok(()) => Ok(true),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(true),
Err(e) => Err(AppError::path(format!(
"Failed to remove symlink '{}': {}",
path.display(),
e
))),
},
_ => Ok(false),
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fail closed when symlink inspection fails.

Line 128 converts permission and I/O errors into Ok(false), allowing callers to continue reading even though the guard could not establish that the path is safe. Only NotFound should be treated as absent.

Proposed fix
     match std::fs::symlink_metadata(path) {
         Ok(meta) if meta.file_type().is_symlink() => match std::fs::remove_file(path) {
             Ok(()) => Ok(true),
             Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(true),
             Err(e) => Err(AppError::path(format!(
                 "Failed to remove symlink '{}': {}",
                 path.display(),
                 e
             ))),
         },
-        _ => Ok(false),
+        Ok(_) => Ok(false),
+        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
+        Err(e) => Err(AppError::path(format!(
+            "Failed to inspect '{}': {}",
+            path.display(),
+            e
+        ))),
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn remove_symlink_if_present(path: &Path) -> Result<bool, AppError> {
match std::fs::symlink_metadata(path) {
Ok(meta) if meta.file_type().is_symlink() => match std::fs::remove_file(path) {
Ok(()) => Ok(true),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(true),
Err(e) => Err(AppError::path(format!(
"Failed to remove symlink '{}': {}",
path.display(),
e
))),
},
_ => Ok(false),
}
pub fn remove_symlink_if_present(path: &Path) -> Result<bool, AppError> {
match std::fs::symlink_metadata(path) {
Ok(meta) if meta.file_type().is_symlink() => match std::fs::remove_file(path) {
Ok(()) => Ok(true),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(true),
Err(e) => Err(AppError::path(format!(
"Failed to remove symlink '{}': {}",
path.display(),
e
))),
},
Ok(_) => Ok(false),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(AppError::path(format!(
"Failed to inspect '{}': {}",
path.display(),
e
))),
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/git-same-core/src/ipc/status_file.rs` around lines 117 - 129, Update
remove_symlink_if_present to distinguish symlink_metadata errors: return
Ok(false) only for ErrorKind::NotFound, and propagate all other inspection
errors as AppError::path failures. Preserve the existing symlink removal
behavior and error handling.

@cubic-dev-ai cubic-dev-ai 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.

6 issues found across 23 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/git-same-core/src/ipc/status_file.rs">

<violation number="1" location="crates/git-same-core/src/ipc/status_file.rs:128">
P2: The `_ => Ok(false)` wildcard catches I/O errors from `symlink_metadata` (e.g. `PermissionDenied`) and treats them the same as "path is a regular file." If the inspection fails for reasons other than `NotFound`, the function can't confirm the path isn't a symlink, so callers may proceed to read through a link they intended to guard against. Consider splitting the wildcard to propagate unexpected errors:

```rust
Ok(_) => Ok(false),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(AppError::path(format!(
    "Failed to inspect '{}': {}",
    path.display(),
    e
))),
```</violation>

<violation number="2" location="crates/git-same-core/src/ipc/status_file.rs:209">
P2: A malformed legacy `status.json` directory now blocks mirror recovery: this migration no longer moves that entry aside, and the non-fatal mirror path leaves the monitor running with the host status permanently stale until the directory is removed. Preserving the prior aside behavior for non-file status entries would keep migration recoverable.</violation>
</file>

<file name="crates/git-same-core/src/ipc/status_file_tests.rs">

<violation number="1" location="crates/git-same-core/src/ipc/status_file_tests.rs:118">
P2: A symlinked mirror would pass this test, so the TCC-avoidance invariant is untested. Checking `symlink_metadata(...).file_type().is_file()` for both destinations would catch a regression that recreates the cross-container symlink.</violation>
</file>

<file name="crates/git-same-core/src/ipc/mod_tests.rs">

<violation number="1" location="crates/git-same-core/src/ipc/mod_tests.rs:107">
P2: The new path tests can fail intermittently under the default parallel test runner when workspace tests change `GIT_SAME_CONFIG_DIR` or `HOME` between these environment-dependent calls. A shared test-environment lock or dependency-injected path source would make the assertions deterministic.</violation>
</file>

<file name="crates/git-same-app/src/commands.rs">

<violation number="1" location="crates/git-same-app/src/commands.rs:1102">
P2: A monitor running a different build is still reported as a passing requirement, so the new restart guidance is hidden in the Requirements UI. Including `monitor_version_mismatch` in the `passed` calculation would make the skew actionable, or the new suggestion branch should be removed if skew is intentionally informational.</violation>
</file>

<file name="crates/git-same-app/Cargo.toml">

<violation number="1" location="crates/git-same-app/Cargo.toml:30">
P3: The `time` version cap `>=0.3, <0.3.52` is tighter than necessary and the rationale comment is slightly stale. `time` 0.3.53 (released 2026-07-01) already reverts the internal `Parsable::parse` breaking change from 0.3.52 to make the `cookie` crate compile again, so 0.3.53+ is now compatible. Consider relaxing the constraint to `>=0.3, !=0.3.52` or updating the comment to note that time 0.3.53 already resolved the issue, and that the cap can be relaxed now rather than waiting for a cookie release.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

}

Ok(())
// Only the socket is symlinked; status.json is a real mirror file written

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: A malformed legacy status.json directory now blocks mirror recovery: this migration no longer moves that entry aside, and the non-fatal mirror path leaves the monitor running with the host status permanently stale until the directory is removed. Preserving the prior aside behavior for non-file status entries would keep migration recoverable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/git-same-core/src/ipc/status_file.rs, line 209:

<comment>A malformed legacy `status.json` directory now blocks mirror recovery: this migration no longer moves that entry aside, and the non-fatal mirror path leaves the monitor running with the host status permanently stale until the directory is removed. Preserving the prior aside behavior for non-file status entries would keep migration recoverable.</comment>

<file context>
@@ -96,27 +186,31 @@ impl StatusFileWriter {
-    }
-
-    Ok(())
+    // Only the socket is symlinked; status.json is a real mirror file written
+    // by the monitor (see the doc comment on `ensure_legacy_symlinks`).
+    let legacy_sock = legacy_dir.join("finder.sock");
</file context>

Comment on lines +118 to +119
assert!(primary.exists());
assert!(mirror.exists());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: A symlinked mirror would pass this test, so the TCC-avoidance invariant is untested. Checking symlink_metadata(...).file_type().is_file() for both destinations would catch a regression that recreates the cross-container symlink.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/git-same-core/src/ipc/status_file_tests.rs, line 118:

<comment>A symlinked mirror would pass this test, so the TCC-avoidance invariant is untested. Checking `symlink_metadata(...).file_type().is_file()` for both destinations would catch a regression that recreates the cross-container symlink.</comment>

<file context>
@@ -104,6 +104,120 @@ fn test_no_temp_file_remains_after_write() {
+    writer.write(&status).unwrap();
+
+    // Both files exist as real files with identical content.
+    assert!(primary.exists());
+    assert!(mirror.exists());
+    assert_eq!(
</file context>
Suggested change
assert!(primary.exists());
assert!(mirror.exists());
let primary_meta = std::fs::symlink_metadata(&primary).unwrap();
let mirror_meta = std::fs::symlink_metadata(&mirror).unwrap();
assert!(primary_meta.file_type().is_file());
assert!(mirror_meta.file_type().is_file());

fn test_host_status_path_matches_legacy_default_path() {
// The host reads from the non-container host path; it must resolve to the
// same directory as legacy_default_path (a distinct name for clarity).
let host = IpcConfig::host_status_path();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The new path tests can fail intermittently under the default parallel test runner when workspace tests change GIT_SAME_CONFIG_DIR or HOME between these environment-dependent calls. A shared test-environment lock or dependency-injected path source would make the assertions deterministic.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/git-same-core/src/ipc/mod_tests.rs, line 107:

<comment>The new path tests can fail intermittently under the default parallel test runner when workspace tests change `GIT_SAME_CONFIG_DIR` or `HOME` between these environment-dependent calls. A shared test-environment lock or dependency-injected path source would make the assertions deterministic.</comment>

<file context>
@@ -99,3 +99,56 @@ fn test_legacy_default_path_ends_in_finder() {
+fn test_host_status_path_matches_legacy_default_path() {
+    // The host reads from the non-container host path; it must resolve to the
+    // same directory as legacy_default_path (a distinct name for clarity).
+    let host = IpcConfig::host_status_path();
+    let legacy = IpcConfig::legacy_default_path();
+    match (host, legacy) {
</file context>

Some(_) if snapshot.is_some_and(|snapshot| snapshot.stale) => {
"Monitor running but status file is stale".to_string()
}
Some(_) if skew.is_some() => format!(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: A monitor running a different build is still reported as a passing requirement, so the new restart guidance is hidden in the Requirements UI. Including monitor_version_mismatch in the passed calculation would make the skew actionable, or the new suggestion branch should be removed if skew is intentionally informational.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/git-same-app/src/commands.rs, line 1102:

<comment>A monitor running a different build is still reported as a passing requirement, so the new restart guidance is hidden in the Requirements UI. Including `monitor_version_mismatch` in the `passed` calculation would make the skew actionable, or the new suggestion branch should be removed if skew is intentionally informational.</comment>

<file context>
@@ -1049,6 +1099,11 @@ fn monitor_requirement_message(
         Some(_) if snapshot.is_some_and(|snapshot| snapshot.stale) => {
             "Monitor running but status file is stale".to_string()
         }
+        Some(_) if skew.is_some() => format!(
+            "Monitor is running a different build ({}) than the app ({})",
+            skew.as_deref().unwrap_or_default(),
</file context>

e
))),
},
_ => Ok(false),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The _ => Ok(false) wildcard catches I/O errors from symlink_metadata (e.g. PermissionDenied) and treats them the same as "path is a regular file." If the inspection fails for reasons other than NotFound, the function can't confirm the path isn't a symlink, so callers may proceed to read through a link they intended to guard against. Consider splitting the wildcard to propagate unexpected errors:

Ok(_) => Ok(false),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(AppError::path(format!(
    "Failed to inspect '{}': {}",
    path.display(),
    e
))),
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/git-same-core/src/ipc/status_file.rs, line 128:

<comment>The `_ => Ok(false)` wildcard catches I/O errors from `symlink_metadata` (e.g. `PermissionDenied`) and treats them the same as "path is a regular file." If the inspection fails for reasons other than `NotFound`, the function can't confirm the path isn't a symlink, so callers may proceed to read through a link they intended to guard against. Consider splitting the wildcard to propagate unexpected errors:

```rust
Ok(_) => Ok(false),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(AppError::path(format!(
    "Failed to inspect '{}': {}",
    path.display(),
    e
))),
```</comment>

<file context>
@@ -83,10 +89,94 @@ impl StatusFileWriter {
+                e
+            ))),
+        },
+        _ => Ok(false),
+    }
+}
</file context>
Suggested change
_ => Ok(false),
Ok(_) => Ok(false),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(AppError::path(format!(
"Failed to inspect '{}': {}",
path.display(),
e
))),

# Pin: tauri's transitive `cookie` 0.18.1 calls time's Parsable::parse with the
# pre-0.3.52 one-arg signature; time 0.3.52 made it two-arg and fails to compile.
# No fixed cookie release exists yet. Remove this cap once cookie ships a fix.
time = ">=0.3, <0.3.52"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The time version cap >=0.3, <0.3.52 is tighter than necessary and the rationale comment is slightly stale. time 0.3.53 (released 2026-07-01) already reverts the internal Parsable::parse breaking change from 0.3.52 to make the cookie crate compile again, so 0.3.53+ is now compatible. Consider relaxing the constraint to >=0.3, !=0.3.52 or updating the comment to note that time 0.3.53 already resolved the issue, and that the cap can be relaxed now rather than waiting for a cookie release.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/git-same-app/Cargo.toml, line 30:

<comment>The `time` version cap `>=0.3, <0.3.52` is tighter than necessary and the rationale comment is slightly stale. `time` 0.3.53 (released 2026-07-01) already reverts the internal `Parsable::parse` breaking change from 0.3.52 to make the `cookie` crate compile again, so 0.3.53+ is now compatible. Consider relaxing the constraint to `>=0.3, !=0.3.52` or updating the comment to note that time 0.3.53 already resolved the issue, and that the cap can be relaxed now rather than waiting for a cookie release.</comment>

<file context>
@@ -24,6 +24,10 @@ serde = { workspace = true }
+# Pin: tauri's transitive `cookie` 0.18.1 calls time's Parsable::parse with the
+# pre-0.3.52 one-arg signature; time 0.3.52 made it two-arg and fails to compile.
+# No fixed cookie release exists yet. Remove this cap once cookie ships a fix.
+time = ">=0.3, <0.3.52"
 tauri-plugin-dialog = "2"
 tokio = { workspace = true }
</file context>
Suggested change
time = ">=0.3, <0.3.52"
time = ">=0.3, !=0.3.52"

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.

2 participants