Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
37fab8b
fix(daemon): eliminate flakiness in daemon and wrapper-daemon test su…
svarlamov Apr 12, 2026
b75e783
fix(daemon): address code review findings — drain wakeup, dead code, …
svarlamov Apr 12, 2026
a61f552
fix(lint): fix rustfmt line-length and clippy collapsible-if
svarlamov Apr 12, 2026
45df33f
fix(tests): wait for all checkpoint drains before commit in race tests
svarlamov Apr 13, 2026
330151c
fix(lint): remove dead completion_count methods, allow too_many_argum…
svarlamov Apr 13, 2026
7ff6c9d
fix(tests): wait for checkpoint drains in high-throughput and concurr…
svarlamov Apr 13, 2026
a58f0a4
fix(tests): run cross-repo checkpoints synchronously in git_ai_from_w…
svarlamov Apr 13, 2026
4300e71
fix(tests): use GIT_AI_CHECKPOINT_FORCE_SYNC to override async_mode i…
svarlamov Apr 13, 2026
19134b5
chore: trigger fresh CI run to verify ubuntu wrapper-daemon flakiness
svarlamov Apr 13, 2026
7bbe1d0
fix(test): use checkpoint-specific count in sync_queued_checkpoint_if…
svarlamov Apr 13, 2026
4be2602
fix(test): reset daemon idle timer on any completion entry, not just …
svarlamov Apr 13, 2026
d15521c
fix(test): increase Windows daemon sync timeouts for heavy CI load
svarlamov Apr 13, 2026
448a0b6
chore: trigger CI re-run #1 to verify flakiness fix
svarlamov Apr 13, 2026
88461e6
fix(test): increase non-Windows daemon sync timeouts for CI load
svarlamov Apr 13, 2026
f85b563
fix(daemon): eliminate scheduling-latency flakiness in daemon mode tests
svarlamov Apr 13, 2026
10fc55c
fix(daemon): apply wrapper state overlay in family sequencer drain path
svarlamov Apr 13, 2026
1d01b56
fix(daemon): use short timeout for wrapper state overlay in drain path
svarlamov Apr 13, 2026
43b2765
fix(daemon): run checkpoint side effect on blocking thread pool
svarlamov Apr 13, 2026
7884fb0
fix(daemon): use try_lock for inline drain to unblock ingest worker
svarlamov Apr 13, 2026
b2daaf7
fix(daemon): spawn one-shot fallback drain task instead of notify_one
svarlamov Apr 13, 2026
73dec01
fix(daemon): remove test-only GIT_AI_CHECKPOINT_FORCE_SYNC escape hatch
svarlamov Apr 14, 2026
0b4128a
fix(daemon): replace FORCE_SYNC with proper post-command checkpoint sync
svarlamov Apr 14, 2026
7731b1b
chore: trigger CI iteration 2 to verify flakiness fix
svarlamov Apr 14, 2026
1b413d8
chore: trigger CI iteration 3 to verify flakiness fix
svarlamov Apr 14, 2026
260d9dd
chore: trigger CI iteration 4 to verify flakiness fix
svarlamov Apr 14, 2026
966fb21
fix(test): disable checkpoint delegation for cross-repo commands
svarlamov Apr 14, 2026
5e43576
chore: trigger CI iteration 2 to verify flakiness fix
svarlamov Apr 14, 2026
38c897b
chore: trigger CI iteration 3 to verify flakiness fix
svarlamov Apr 14, 2026
c2f44bb
chore: trigger CI iteration 4 to verify flakiness fix
svarlamov Apr 14, 2026
a45a96d
chore: trigger CI iteration 3 to verify flakiness fix
svarlamov Apr 14, 2026
8adf0cd
chore: trigger CI iteration 4 to verify flakiness fix
svarlamov Apr 14, 2026
3cd9ef7
fix(daemon): always write completion log on Applied path even when si…
svarlamov Apr 14, 2026
5e6d7fe
fix(daemon): write completion log for drain path error/panic cases
svarlamov Apr 14, 2026
11d547b
fix(daemon): acquire exec_lock on Applied path to prevent concurrent …
svarlamov Apr 14, 2026
795203d
chore: trigger CI exec_lock iteration 1
svarlamov Apr 14, 2026
acf9147
chore: trigger CI exec_lock iteration 2
svarlamov Apr 14, 2026
870b50e
chore: trigger CI exec_lock iteration 3
svarlamov Apr 14, 2026
ac1b4c8
fix(daemon): use try_lock on Applied path to avoid blocking ingest wo…
svarlamov Apr 14, 2026
54e7394
fix(notes): retry fast-import on refs/notes/ai ref conflicts
svarlamov Apr 14, 2026
faa43a7
fix(daemon): add catch_unwind + fallback completion to Applied path
svarlamov Apr 14, 2026
bbe0926
chore: trigger CI catch_unwind iteration 2
svarlamov Apr 14, 2026
d236fe8
chore: trigger CI catch_unwind iteration 3
svarlamov Apr 14, 2026
cddb505
chore: trigger CI catch_unwind iteration 4
svarlamov Apr 14, 2026
5ade0b2
fix(daemon): scan all rewrite events for preceding merge squash
svarlamov Apr 14, 2026
ccab563
fix(daemon): spawn tasks for side effects instead of inline processing
svarlamov Apr 14, 2026
d0439c0
fix(daemon): restore QueuedFamily inline drain for latency
svarlamov Apr 14, 2026
b3705e3
fix(daemon): use drain worker instead of inline drain for QueuedFamily
svarlamov Apr 14, 2026
80c74ef
fix(daemon): use short wrapper-state timeout for Applied-path spawned…
svarlamov Apr 14, 2026
fdfa27d
chore: trigger CI iteration 2 for 80c74ef0 fixes
svarlamov Apr 14, 2026
b9a9b93
chore: trigger CI iteration 3
svarlamov Apr 14, 2026
69de982
fix(daemon): use try_lock inline for Applied path, spawn only on cont…
svarlamov Apr 14, 2026
0a3e03d
chore: trigger CI iteration 4
svarlamov Apr 14, 2026
c91b35b
chore: trigger CI iteration 5
svarlamov Apr 14, 2026
70269d3
fix(daemon): add catch_unwind + fallback completion log to Applied in…
svarlamov Apr 15, 2026
36f486d
chore: trigger CI iteration 6 (catch_unwind inline path)
svarlamov Apr 15, 2026
b6a7c16
chore: trigger CI iteration 7
svarlamov Apr 15, 2026
bb107e2
chore: trigger CI iteration 8
svarlamov Apr 15, 2026
318f270
chore: trigger CI iteration 9
svarlamov Apr 15, 2026
a54a5cb
fix(daemon): always spawn Applied-path side effects off the ingest wo…
svarlamov Apr 15, 2026
b5d9a19
chore: trigger CI iteration 10 (always-spawn Applied)
svarlamov Apr 15, 2026
a1e8ba5
chore: trigger CI iteration 11
svarlamov Apr 15, 2026
6dde775
chore: trigger CI iteration 12
svarlamov Apr 15, 2026
f5a375e
chore: trigger CI iteration 10 (always-spawn Applied path)
svarlamov Apr 15, 2026
9d8dd78
fix(daemon): write fallback completion log when route_command fails
svarlamov Apr 15, 2026
b51c5d3
fix(daemon): use spawn_blocking for command side effects to prevent T…
svarlamov Apr 15, 2026
f886b0a
fix(daemon): offload carryover snapshot capture to blocking thread in…
svarlamov Apr 15, 2026
bcf31e2
fix(daemon): use block_in_place for normalizer ingest to prevent Toki…
svarlamov Apr 15, 2026
6a111b0
fix(daemon): ensure minimum 4 Tokio worker threads for daemon runtime
svarlamov Apr 15, 2026
eff0ea1
fix(daemon): eliminate env var race in checkpoint timeout unit tests
svarlamov Apr 15, 2026
7722c14
fix(daemon): cancel stale PendingRoot entries on ingest error to prev…
svarlamov Apr 15, 2026
59071cd
fix(daemon): run PendingRoot cancellation inline instead of spawning
svarlamov Apr 15, 2026
18d8783
fix(daemon): prevent ingest worker stall from lost sequence numbers
svarlamov Apr 15, 2026
6ae506b
fix(daemon): make finalize_root_exit resilient to partial reflog data
svarlamov Apr 15, 2026
320f961
fix(daemon): retry post_repo HEAD state read during augmentation
svarlamov Apr 15, 2026
1a50748
fix(daemon): recover post_repo HEAD in normalizer finalize fallback
svarlamov Apr 16, 2026
f7ece98
fix(daemon): always emit git_ai_post_repo for terminal events
svarlamov Apr 16, 2026
68d524f
fix(daemon): increase HEAD read retry budgets for flake resilience
svarlamov Apr 16, 2026
94f80f2
fix(daemon): add git rev-parse HEAD subprocess fallback for post_repo
svarlamov Apr 16, 2026
6934d6c
fix(daemon): remove normalizer HEAD recovery fallback
svarlamov Apr 16, 2026
0b3e921
fix(wrapper): add HEAD read retries and git rev-parse fallback
svarlamov Apr 16, 2026
51ddf42
fix(authorship): retry reading original commit note during amend rewrite
svarlamov Apr 16, 2026
aad9997
fix(daemon): add normalizer-level HEAD recovery using invocation_work…
svarlamov Apr 16, 2026
1965069
fix(daemon): add pre-reducer HEAD recovery as last-resort fallback
svarlamov Apr 16, 2026
8c051bb
fix(daemon): add retries to pre-reducer HEAD recovery and use block_i…
svarlamov Apr 16, 2026
64c0298
fix(tests): use dedicated daemons for ignore_prompts config-sensitive…
svarlamov Apr 16, 2026
529482c
fix(daemon): recover HEAD when worktree overwritten by child def_repo
svarlamov Apr 16, 2026
329ac7a
fix(daemon): exclude clone/init from worktree-overwrite recovery
svarlamov Apr 16, 2026
e35eed2
fix(daemon): fire worktree-overwrite recovery for all command types
svarlamov Apr 16, 2026
47f7c97
fix(daemon): canonicalize paths in worktree-overwrite detection
svarlamov Apr 16, 2026
646529f
fix(daemon): use family key comparison for worktree-overwrite detection
svarlamov Apr 16, 2026
2c95af8
fix(daemon): use invocation worktree for augmentation HEAD reads
svarlamov Apr 16, 2026
6635f1b
Merge remote-tracking branch 'origin/main' into fix/daemon-wrapper-da…
svarlamov Apr 16, 2026
480e6fa
fix(daemon): restore worktree path on same-family cross-worktree over…
svarlamov Apr 16, 2026
50be23d
fix(daemon): only restore absolute invocation worktrees on same-famil…
svarlamov Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions src/authorship/rebase_authorship.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2863,13 +2863,28 @@ pub fn rewrite_authorship_after_commit_amend_with_snapshot(
let touched_files = working_log.all_touched_files()?;
pathspecs.extend(touched_files);

// Check if original commit has an authorship log with prompts or humans
let has_existing_log = get_reference_as_authorship_log_v3(repo, original_commit).is_ok();
let has_existing_data = if has_existing_log {
let original_log = get_reference_as_authorship_log_v3(repo, original_commit).unwrap();
!original_log.metadata.prompts.is_empty() || !original_log.metadata.humans.is_empty()
} else {
false
// Check if original commit has an authorship log with prompts or humans.
// Under heavy I/O the note written by a prior command's side-effect pipeline
// may be transiently unreadable (loose object not yet visible to a new git
// subprocess, or notes ref update still in flight). Retry briefly before
// falling back to the no-existing-data path.
let (has_existing_log, has_existing_data) = {
let mut found_log = false;
let mut found_data = false;
for attempt in 0..=20 {
if let Ok(original_log) =
get_reference_as_authorship_log_v3(repo, original_commit)
{
found_log = true;
found_data = !original_log.metadata.prompts.is_empty()
|| !original_log.metadata.humans.is_empty();
break;
}
if attempt < 20 {
std::thread::sleep(std::time::Duration::from_millis(25));
}
}
Comment on lines +2874 to +2886
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot Apr 16, 2026

Choose a reason for hiding this comment

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

🔴 Amend retry loop adds 500ms latency for commits without existing authorship notes

The retry loop at src/authorship/rebase_authorship.rs:2812-2824 always runs all 21 iterations (20 × 25ms = 500ms of sleep) when get_reference_as_authorship_log_v3 returns Err — it cannot distinguish "note does not exist" (permanent) from "note is transiently unreadable" (temporary I/O race). For the common case of amending a commit that was never AI-attributed (no refs/notes/ai entry), this adds 500ms to every git commit --amend in a git-ai tracked repository. The old code checked once and moved on.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

(found_log, found_data)
};

// Phase 1: Load all attributions (committed + uncommitted)
Expand Down
11 changes: 11 additions & 0 deletions src/commands/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,18 @@ fn handle_run(args: &[String]) -> Result<(), String> {
e
)
})?;
// Ensure at least 4 Tokio worker threads regardless of CPU count.
// The daemon runs concurrent tasks (ingest worker, per-family drain
// workers, applied-path handlers, control API) that all need async
// thread pool access. On 2-core machines the default of 2 threads
// causes starvation when the ingest worker blocks during normalizer
// I/O and drain workers compete for the remaining thread.
let worker_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
.max(4);
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(worker_threads)
.enable_all()
.build()
.map_err(|e| e.to_string())?;
Expand Down
42 changes: 41 additions & 1 deletion src/commands/git_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,49 @@ pub fn handle_git(args: &[String]) {

let exit_status = proxy_to_git(args, false, None, Some(&invocation_id));

let post_state = worktree
let mut post_state = worktree
.as_deref()
.and_then(crate::git::repo_state::read_head_state_for_worktree);
// Under heavy I/O the ref file may be transiently unresolvable.
// Retry so the daemon overlay receives a valid HEAD OID.
if post_state.as_ref().is_none_or(|s| s.head.is_none()) {
for _ in 0..25 {
std::thread::sleep(std::time::Duration::from_millis(20));
post_state = worktree
.as_deref()
.and_then(crate::git::repo_state::read_head_state_for_worktree);
if post_state.as_ref().is_some_and(|s| s.head.is_some()) {
break;
}
}
}
// Last resort: use git rev-parse HEAD subprocess.
if post_state.as_ref().is_none_or(|s| s.head.is_none()) {
if let Some(wt) = worktree.as_deref() {
if let Ok(output) = std::process::Command::new("git")
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.

🟡 Fallback rev-parse HEAD uses bare "git" instead of config::Config::get().git_cmd(), causing unnecessary recursion through git-ai shim

The new fallback subprocess at src/commands/git_handlers.rs:189 uses std::process::Command::new("git") to invoke git rev-parse HEAD. When git-ai is installed as the primary git shim (e.g. symlinked at ~/.local/bin/git), PATH resolution finds the git-ai binary, causing the subprocess to re-enter handle_git() before eventually calling real git. This adds an unnecessary extra process in the chain.

Every other git invocation in this file uses config::Config::get().git_cmd() (see src/commands/git_handlers.rs:871 and src/commands/git_handlers.rs:910), which resolves the real git binary by probing standard paths and explicitly filtering out git-ai binaries. The exec_git family of functions in src/git/repository.rs:3136 also uses this pattern.

Suggested change
if let Ok(output) = std::process::Command::new("git")
if let Ok(output) = std::process::Command::new(crate::config::Config::get().git_cmd())
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

.args(["rev-parse", "HEAD"])
.current_dir(wt)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.output()
Comment on lines +197 to +202
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot Apr 16, 2026

Choose a reason for hiding this comment

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

🟡 Command::new("git") in git wrapper may recursively invoke git-ai proxy

In the async-mode branch of handle_git(), the HEAD recovery fallback uses std::process::Command::new("git") which resolves via PATH. When git-ai is installed as the system git proxy (the normal production deployment), this spawns another git-ai process rather than the real git. The rest of the wrapper code uses config::Config::get().git_cmd() (e.g. src/commands/git_handlers.rs:871) to resolve the actual git binary. While the recursion terminates (the inner git-ai classifies rev-parse as read-only and proxies to real git), it adds an unnecessary extra process spawn, may inherit trace2 env vars generating spurious daemon events, and is inconsistent with the established convention documented in AGENTS.md ("Git CLI over libgit2 in production: All git operations use std::process::Command to call the real git binary" — via Config::get().git_cmd()).

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

{
let oid = String::from_utf8_lossy(&output.stdout).trim().to_string();
if output.status.success()
&& crate::git::repo_state::is_valid_git_oid(&oid)
{
let (branch, detached) = post_state
.as_ref()
.map(|s| (s.branch.clone(), s.detached))
.unwrap_or((None, false));
post_state = Some(crate::git::repo_state::HeadState {
head: Some(oid),
branch,
detached,
});
}
}
}
}

send_wrapper_post_state_to_daemon(&invocation_id, worktree.as_deref(), &post_state);

Expand Down
Loading
Loading