Skip to content

fix: LSP git diff subprocess timeout (#13)#16

Merged
EffortlessSteven merged 1 commit into
feat/v0.2-enhancements-v2from
fix/lsp-git-diff-timeout
Apr 5, 2026
Merged

fix: LSP git diff subprocess timeout (#13)#16
EffortlessSteven merged 1 commit into
feat/v0.2-enhancements-v2from
fix/lsp-git-diff-timeout

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Member

Fix #13: LSP git diff subprocess timeout

Problem

Command::new("git").output() blocks the LSP server indefinitely on large repos. No timeout configured.

Fix

Changed run_git_diff() in crates/diffguard-lsp/src/server.rs:

  • Spawn the process + poll try_wait() in a loop (100ms intervals)
  • 10-second maximum timeout before killing the child process
  • No new dependencies required (uses only std::process::Command, std::thread, std::time::Instant)

Verification

cargo build -p diffguard-lsp   ✓  
cargo clippy -p diffguard-lsp  ✓
cargo fmt --check              ✓
cargo test -p diffguard-lsp    ✓  (9 tests)

Also included

Fixes a race condition in cmd_validate test by adding ENV_LOCK to prevent parallel test interference

Fix #13: LSP git diff subprocess timeout

- Changed run_git_diff() to use spawn() + try_wait() polling loop
  with a 10-second timeout instead of blocking Command::output()
- Spawns git diff process, polls try_wait() every 100ms
- After 10s, kills the child process and returns an error
- No new dependencies needed

Also fixes a race condition in cmd_validate test by adding ENV_LOCK.
@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cf13c37a-6336-4509-a05a-0dd217348af6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/lsp-git-diff-timeout

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 and usage tips.

fn cmd_validate_accepts_valid_globs_and_strict_no_warnings() {
// Acquire ENV_LOCK to prevent DIFFGUARD_TEST_FORCE_COMPILE_ERROR from
// leaking from cmd_validate_forced_compile_error when tests run in parallel.
let _guard = ENV_LOCK.lock().unwrap();

Check failure

Code scanning / diffguard

Avoid unwrap/expect in production code. Error

Avoid unwrap/expect in production code.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a 10-second timeout for the git diff command in the LSP server and adds a mutex lock to a CLI test to prevent environment variable leakage during parallel execution. Feedback focuses on the git diff implementation, noting that the child process should capture output to avoid corrupting the LSP stream, must be reaped after being killed to prevent zombie processes, and should avoid blocking the main thread or deadlocking on pipe buffers.

let output = command.output().context("run git diff")?;
// Spawn with a 10-second timeout to avoid blocking the LSP indefinitely
const GIT_DIFF_TIMEOUT: Duration = Duration::from_secs(10);
let mut child = command.spawn().context("spawn git diff")?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

Using command.spawn() without capturing output causes the child process to inherit the LSP server's standard streams. Since the LSP communicates with the client via stdout, any output from git diff will be written directly to the communication channel, corrupting the protocol stream and likely causing the client to disconnect. You must configure the command to capture stdout and stderr using Stdio::piped().

    let mut child = command
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .context("spawn git diff")?;

Comment on lines +933 to +936
if Instant::now() >= deadline {
let _ = child.kill();
bail!("git diff timed out after {}s", GIT_DIFF_TIMEOUT.as_secs());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Failing to reap a killed process leaves a 'zombie' in the process table. In a long-running LSP server, this can lead to resource exhaustion over time. It is important to call child.wait() after child.kill() to ensure the process is properly reaped by the operating system.

Suggested change
if Instant::now() >= deadline {
let _ = child.kill();
bail!("git diff timed out after {}s", GIT_DIFF_TIMEOUT.as_secs());
}
if Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
bail!("git diff timed out after {}s", GIT_DIFF_TIMEOUT.as_secs());
}

Comment on lines +925 to +941
let output = loop {
match child.try_wait() {
Ok(Some(_)) => {
// Process has exited; wait_with_output() returns immediately
break child.wait_with_output().context("wait for git diff")?;
}
Ok(None) => {
// Still running
if Instant::now() >= deadline {
let _ = child.kill();
bail!("git diff timed out after {}s", GIT_DIFF_TIMEOUT.as_secs());
}
thread::sleep(Duration::from_millis(100));
}
Err(e) => bail!("checking git diff status: {e}"),
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This polling loop blocks the main LSP message processing thread for up to 10 seconds, making the server unresponsive to other requests (such as cancellations) during this time. Furthermore, if the git diff output exceeds the system's pipe buffer size (often 64KB), the child process will block indefinitely because the loop does not read from the pipes while waiting. This will eventually trigger the timeout and cause the operation to fail even if it would have succeeded otherwise. Consider offloading the process execution to a separate thread and using a channel with recv_timeout to handle the result safely.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f749b1b44d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let output = command.output().context("run git diff")?;
// Spawn with a 10-second timeout to avoid blocking the LSP indefinitely
const GIT_DIFF_TIMEOUT: Duration = Duration::from_secs(10);
let mut child = command.spawn().context("spawn git diff")?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pipe git diff output before waiting

run_git_diff now uses command.spawn() without configuring stdio, so git inherits the LSP process stdout/stderr. In Rust this means wait_with_output() returns empty buffers, so successful diffs are seen as empty and diagnostics are suppressed, and the raw unified diff is emitted onto the server’s stdout stream (which can corrupt stdio-based LSP messaging) whenever a file has git changes.

Useful? React with 👍 / 👎.

@EffortlessSteven EffortlessSteven merged commit fb13941 into feat/v0.2-enhancements-v2 Apr 5, 2026
8 of 10 checks passed
@EffortlessSteven EffortlessSteven deleted the fix/lsp-git-diff-timeout branch April 5, 2026 22:39
EffortlessSteven added a commit that referenced this pull request Apr 5, 2026
Bring in PRs #14 (config DAG support + field-wise defaults merge) and
#15 (xtask binary path resolution + mutex poison recovery) from main.

Conflict resolution: take HEAD (feat branch) changes for server.rs
which has the git diff timeout fix from PR #16 that is not yet on main.
EffortlessSteven added a commit that referenced this pull request Apr 6, 2026
… analytics (#5)

* Implement environment variable expansion for configuration files and add BDD integration tests for CLI workflows

* Add conformance tests and schema validation for sensor report

- Introduced new snapshots for sensor report findings and annotations.
- Created a JSON schema for the sensor report format.
- Implemented conformance tests to validate sensor report output against the schema.
- Added tests for determinism, survivability, required fields, and vocabulary compliance.
- Enhanced the test setup to create minimal git repositories and trigger findings.

* feat: Add timing metrics to CheckReceipt and update related tests

- Introduced `timing` field in `CheckReceipt` struct to capture performance metrics.
- Updated tests in `schema.rs` and `properties.rs` to initialize `timing` with `None`.
- Enhanced `RuleConfig` struct with `tags` field for better rule categorization.
- Added multiple new rules across various languages (Rust, Python, JavaScript, Ruby, Java, C#) to enforce best practices and security.
- Implemented SARIF integration in GitHub workflows for better visibility of findings.
- Created Azure DevOps pipeline templates for diffguard to facilitate governance checks.

* feat: Enhance configuration management and validation

- Added support for environment variable expansion in configuration files.
- Implemented a new `config_loader` module to handle includes and circular include detection.
- Introduced `Validate` command to validate configuration files, checking for duplicate rule IDs and valid regex patterns.
- Added `Test` command to run test cases defined in rule configurations.
- Enhanced logging capabilities with `tracing` for better debugging and information output.
- Updated schemas to include new fields for tags and timing metrics.
- Improved integration tests for suppression directives and configuration loading.

* feat: Update fingerprint computation to use full SHA-256 hash and enhance related tests

* Add snapshot tests for JSON receipt and GitHub annotation formats

- Implemented snapshot tests for various scenarios in JSON receipt output, including:
  - No findings
  - Warnings only
  - Errors only
  - Mixed severity findings
  - Findings with suppressions
  - Info-only findings
  - Findings without column info

- Added snapshot tests for GitHub annotation format covering:
  - Empty annotations
  - Annotations with info, warning, and error findings
  - Annotations with multiple findings across different files
  - Annotations with special characters and deeply nested paths

* Refactor verdict reasons handling in markdown rendering

- Introduced a constant for renderable meta reasons to filter verdict reasons in markdown output.
- Updated `render_markdown_for_receipt` to only display relevant meta reasons.
- Modified tests to ensure non-meta reasons are excluded from markdown output.
- Adjusted sensor report structure to include detailed reasons for capability status.
- Updated JSON schema to reflect changes in sensor report structure.
- Removed deprecated reasons from verdicts and ensured backward compatibility.
- Added schema drift detection to maintain consistency between generated and contract schemas.

* feat: Refactor error handling and improve JSON schema drift diagnostics

* Refactor code structure for improved readability and maintainability

* Refactor code structure for improved readability and maintainability

* chore: remove accidental build artifacts and binaries

* chore: reorder imports for consistency and readability across multiple files

* chore: fmt, clippy fixes and unsafe blocks

* chore: update authors and repository URLs in Cargo.toml

* feat: add directory rule overrides and related tests

- Introduced DirectoryRuleOverride struct for managing per-directory rule overrides from `.diffguard.toml`.
- Implemented RuleOverrideMatcher for compiling and resolving effective overrides based on directory structure.
- Added integration tests for directory overrides to validate behavior in various scenarios.
- Created fuzz targets for config parsing, glob and regex pattern compilation, and rule matching to enhance robustness against edge cases.
- Updated Cargo.toml with additional metadata for the xtask package.

* feat: Enhance rule configuration and CLI options for multi-base diff checks

- Added support for multi-base comparisons in the `--base` option, allowing multiple base refs to be specified.
- Introduced `--diff-file` option to read unified diffs from a file or stdin.
- Updated `README.md` and `diffguard.toml.example` to reflect new configuration options and advanced semantics.
- Enhanced rule configuration with new fields: `match_mode`, `multiline`, `context_patterns`, `escalate_patterns`, and `depends_on`.
- Improved validation logic for rules, including checks for context and escalation patterns.
- Added per-rule hit statistics output option to the CLI.
- Updated design and requirements documentation to include new features and behaviors.
- Implemented changes in fuzz targets to accommodate new rule fields.
- Created a sample commit-msg hook to enforce commit message standards and staged diff checks.

* Implement LSP server for diffguard with document state management and code action support

- Added server functionality in `server.rs` to handle LSP requests and notifications.
- Implemented document state management to track changes and apply incremental updates.
- Introduced code action capabilities to provide explanations and links to documentation for rules.
- Created utility functions in `text.rs` for handling text changes, building diffs, and calculating changed lines.
- Added tests for core functionalities including changed line detection and synthetic diff generation.

* refactor(lsp): split into lib+bin targets, fix initialize handshake

- Add [lib] and [[bin]] sections for integration testing support
- Use initialize_start/initialize_finish to properly send server_info
- Add diffguard-testkit and insta as dev-dependencies
- Re-export server module from lib.rs

* fix(xtask): cargo_bin_path fallback to target/debug when CARGO_BIN_EXE not set

The conformance tests (survivability, tool_error_code) were failing when
run via cargo test -p xtask because CARGO_BIN_EXE_diffguard is only set
when diffguard is a direct dependency of the test binary. xtask depends
on diffguard-types and diffguard-analytics but not diffguard itself.

Fix: cargo_bin_path() now mirrors run_diffguard() by building the binary
via ensure_diffguard_built() and resolving the path in target/debug/
when the env var isn't available.

* fix: remove duplicate init_logging call (#10)

* chore: add conveyor governance templates and CI gates

- Issue templates (feature/bug) with scope and acceptance criteria
- PR template with gate checklists (Designed/Proven/Hardened)
- Default PR template for non-conveyor contributors
- CI workflow: separate fmt/clippy/test jobs with named status checks
- CI gate: issue linkage check (PR must reference an issue)
- CI gate: branch naming convention (warns on non-standard names)
- Branch protection: requires Format, Clippy, Test, Issue linked
- CONTRIBUTING.md documenting the governance flow
- CODEOWNERS for templates and CI

* docs: add DESIGN.md — governance primitive layer positioning

* fix: LSP git diff timeout and cmd_validate ENV_LOCK race (#16)

Fix #13: LSP git diff subprocess timeout

- Changed run_git_diff() to use spawn() + try_wait() polling loop
  with a 10-second timeout instead of blocking Command::output()
- Spawns git diff process, polls try_wait() every 100ms
- After 10s, kills the child process and returns an error
- No new dependencies needed

Also fixes a race condition in cmd_validate test by adding ENV_LOCK.
EffortlessSteven added a commit that referenced this pull request Apr 6, 2026
Archives the full reasoning trail for landing v0.2 enhancements:
- Issue triage (#6-#13)
- Fix PRs (#14, #15, #16)
- Merge process, friction log, stats

Index in .hermes/index.md for discoverability.
EffortlessSteven added a commit that referenced this pull request Apr 6, 2026
Archives the full reasoning trail for landing v0.2 enhancements:
- Issue triage (#6-#13)
- Fix PRs (#14, #15, #16)
- Merge process, friction log, stats

Index in .hermes/index.md for discoverability.
EffortlessSteven added a commit that referenced this pull request Apr 6, 2026
* chore: add flow/plan docs for PR #5 merge prep

Archives the full reasoning trail for landing v0.2 enhancements:
- Issue triage (#6-#13)
- Fix PRs (#14, #15, #16)
- Merge process, friction log, stats

Index in .hermes/index.md for discoverability.

* chore: consolidate agent context into .hermes/agent-context.md

AGENTS.md, CLAUDE.md, GEMINI.md were 90% identical with minor wording
differences. Canonicalized into single .hermes/agent-context.md with
thin pointer files at root for agent discovery.

Added .hermes/index.md entry for agent-context.md.
EffortlessSteven added a commit that referenced this pull request Apr 6, 2026
* chore: add flow/plan docs for PR #5 merge prep

Archives the full reasoning trail for landing v0.2 enhancements:
- Issue triage (#6-#13)
- Fix PRs (#14, #15, #16)
- Merge process, friction log, stats

Index in .hermes/index.md for discoverability.

* chore: consolidate agent context into .hermes/agent-context.md

AGENTS.md, CLAUDE.md, GEMINI.md were 90% identical with minor wording
differences. Canonicalized into single .hermes/agent-context.md with
thin pointer files at root for agent discovery.

Added .hermes/index.md entry for agent-context.md.

* chore: add publish script and release notes for v0.2.0

* chore: clean up Cargo.toml package metadata for crates.io

- Remove redundant exclude blocks (include is an allowlist, exclude is ignored)
- Remove CLAUDE.md from include lists (thin pointers, not needed in packages)
- Fix include/exclude conflict warning

* chore: fix crate includes for crates.io, add publish workflow

Remove ../../ paths from include fields (crates.io only packages
crate-local files). Add GitHub Actions publish workflow triggered
on v* tag push with verify → publish → release stages.

* chore: remove include fields, let cargo use git-tracked defaults
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