Skip to content

Propagate silently-swallowed errors in credential/profile writes - #229

Merged
getappz merged 2 commits into
masterfrom
devin/1784314581-error-handling
Jul 17, 2026
Merged

Propagate silently-swallowed errors in credential/profile writes#229
getappz merged 2 commits into
masterfrom
devin/1784314581-error-handling

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes three places where errors that matter were silently swallowed, defeating the durability/integrity guarantees the surrounding code advertises. All are in security-/data-sensitive paths (credential vault writes, the user's shell profile, isolate setup).

1. atomic_fs.rs — durability & permission errors dropped. The module doc promises a "durable, crash-atomic write" for credential files, yet both try_atomic_write and in_place_overwrite did:

let _ = f.flush();
let _ = f.sync_all();          // durability silently not guaranteed
...
let _ = std::fs::set_permissions(&tmp, perms.clone());  // 0600 silently not applied

A dropped sync_all means the "durable" write may never reach disk; a dropped set_permissions means a credential file the doc explicitly wants at 0600 can silently be left world-readable. These now propagate via ?. try_atomic_write's write/perms/rename sequence is wrapped in a closure so the temp file is still cleaned up on any failure (previously only the rename path cleaned up).

2. alias.rs — unreadable shell profile treated as empty (data loss). run() did std::fs::read_to_string(&profile).ok(), collapsing a real read error into None. write_managed_block then treats None as empty content and overwrites the file with just the managed block — so an existing but unreadable ~/.bashrc (permissions, transient I/O error) would be clobbered. Now only NotFound maps to None; any other error aborts with a message (respecting the existing --json output contract).

3. auth.rs — silent failures during isolate setup. symlink_or_copy did .ok(), so a failure to link .ssh/.gitconfig/.git-credentials into an isolate passed silently, leaving the isolate missing the host state it needs. The isolate.json metadata write also used .ok(), and without it read_isolate_mode later can't tell shallow from deep. Both now emit warnings (an already-present link target is still treated as fine).

Test plan

  • cargo test (all pass)
  • cargo clippy --locked --workspace --all-targets --all-features -- -D warnings -A unsafe_code -A clippy::pedantic (clean; matches CI's gate)
  • cargo fmt --check

Notes for reviewers

  • Risk areas / edge cases: propagating sync_all/set_permissions now turns previously-ignored failures into real errors. On normal filesystems these don't fail; on exotic ones (some network FS) a sync_all error would now surface instead of a silent, non-durable write — which is the intended behavior for this module. write_bytes_with_fallback already wraps and reports these.
  • Backwards compatibility: no signature changes. alias.rs now exits non-zero (or emits an error status in --json mode) instead of clobbering an unreadable profile; the isolate changes only add warnings on the previously-silent failure paths.

Link to Devin session: https://app.devin.ai/sessions/e555a41244c34e41a11e2b4cfa4cf706
Requested by: @getappz

Summary by CodeRabbit

  • Bug Fixes
    • Profile read failures now report an error instead of being treated as an empty profile.
    • File-writing operations now detect and report failures during flushing, syncing, permission updates, and replacement.
    • Failed isolate metadata writes and host-file linking or copying now generate warnings instead of being silently ignored.
    • Temporary files are cleaned up when atomic writes fail.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@getappz getappz self-assigned this Jul 17, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The changes replace silently ignored profile, filesystem durability, metadata-writing, and isolate linking failures with explicit errors, cleanup, early returns, or warnings.

Changes

Error handling improvements

Layer / File(s) Summary
Propagate durable write failures
src/atomic_fs.rs
Atomic and in-place writes now propagate flush, synchronization, permission, and cleanup failures.
Report profile and isolate failures
src/alias.rs, src/auth.rs
Non-missing profile read errors stop processing with an error, while isolate metadata and link failures emit warnings.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • getappz/agentflare#175: Both changesets modify atomic write durability and error handling in src/atomic_fs.rs.

Suggested reviewers: getappz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing silently swallowed write errors in credential, profile, and isolate paths.
Description check ✅ Passed The description follows the template and includes complete Summary, Test plan, and Notes for reviewers sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 devin/1784314581-error-handling

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

@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: 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 `@src/atomic_fs.rs`:
- Around line 96-120: Update the atomic write flow around the temporary-file
creation and cleanup so `remove_file(&tmp)` runs only after this invocation
successfully creates the temp file; handle `OpenOptions::open` separately before
the closure, preserving `AlreadyExists` without deleting another writer’s file.
When cleanup is required, propagate or combine any cleanup failure with the
original operation error instead of discarding it.

In `@src/auth.rs`:
- Around line 1083-1085: Update the Windows symlink_or_copy function to detect
src.is_dir() before invoking fs::copy. For directory sources, use the existing
directory-aware symlink or copy behavior so isolate directories such as .cache
and Documents are preserved; retain the current fs::copy path for files and pass
the resulting status through warn_on_link_failure.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 058c581c-d797-469d-83e7-4684d8c5f192

📥 Commits

Reviewing files that changed from the base of the PR and between a9d3095 and 990a8ad.

📒 Files selected for processing (3)
  • src/alias.rs
  • src/atomic_fs.rs
  • src/auth.rs

Comment thread src/atomic_fs.rs
Comment on lines +96 to 120
let result = (|| -> std::io::Result<()> {
{
let mut f = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&tmp)?;
f.write_all(bytes)?;
f.flush()?;
f.sync_all()?;
}

if let Some(perms) = permissions {
let _ = std::fs::set_permissions(&tmp, perms.clone());
}
if let Some(perms) = permissions {
std::fs::set_permissions(&tmp, perms.clone())?;
}

// std::fs::rename already replaces an existing destination on every
// platform we build for (including Windows, via MoveFileExW's
// MOVEFILE_REPLACE_EXISTING) — no separate pre-removal needed, and one
// would only open a window where neither the old nor new file exists.
if let Err(e) = std::fs::rename(&tmp, path) {
// Don't leave a half-written temp behind before the caller decides
// whether to fall back.
// std::fs::rename already replaces an existing destination on every
// platform we build for (including Windows, via MoveFileExW's
// MOVEFILE_REPLACE_EXISTING) — no separate pre-removal needed, and one
// would only open a window where neither the old nor new file exists.
std::fs::rename(&tmp, path)
})();

if result.is_err() {
let _ = std::fs::remove_file(&tmp);
return Err(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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Only clean up a temp file after this invocation successfully creates it.

Because open(&tmp)? is inside the closure, AlreadyExists also reaches Line 119 and may delete another concurrent writer’s temp file. Move creation outside the closure, then surface any cleanup failure alongside the original error rather than discarding it.

🤖 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 `@src/atomic_fs.rs` around lines 96 - 120, Update the atomic write flow around
the temporary-file creation and cleanup so `remove_file(&tmp)` runs only after
this invocation successfully creates the temp file; handle `OpenOptions::open`
separately before the closure, preserving `AlreadyExists` without deleting
another writer’s file. When cleanup is required, propagate or combine any
cleanup failure with the original operation error instead of discarding it.

Comment thread src/auth.rs
Comment on lines 1083 to +1085
#[cfg(windows)]
fn symlink_or_copy(src: &std::path::Path, dest: &std::path::Path) {
fs::copy(src, dest).ok();
warn_on_link_failure(src, dest, fs::copy(src, dest).map(|_| ()));

Copy link
Copy Markdown

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
curl -fsSL https://doc.rust-lang.org/std/fs/fn.copy.html |
  python -c 'import sys; text=sys.stdin.read(); assert "neither a regular file nor a symlink to a regular file" in text'

Repository: getappz/agentflare

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant function and surrounding lines.
sed -n '1040,1125p' src/auth.rs

printf '\n---- CALLERS ----\n'

# Find call sites for symlink_or_copy.
grep -RIn --exclude-dir=.git "symlink_or_copy" src || true

Repository: getappz/agentflare

Length of output: 3518


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search the repository for `symlink_or_copy`.
grep -RIn --exclude-dir=.git "symlink_or_copy" .

printf '\n---- src/auth.rs around the symbol ----\n'
grep -n "symlink_or_copy" -n src/auth.rs || true

Repository: getappz/agentflare

Length of output: 797


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '760,835p' src/auth.rs

Repository: getappz/agentflare

Length of output: 2523


Handle directory sources on Windows before calling fs::copy

fs::copy skips directories, so the Windows isolate never links .cache, .config, .local, Documents, Downloads, or .ssh; those paths are just warned about and omitted. Use a directory-aware copy/symlink path when src.is_dir().

🤖 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 `@src/auth.rs` around lines 1083 - 1085, Update the Windows symlink_or_copy
function to detect src.is_dir() before invoking fs::copy. For directory sources,
use the existing directory-aware symlink or copy behavior so isolate directories
such as .cache and Documents are preserved; retain the current fs::copy path for
files and pass the resulting status through warn_on_link_failure.

@getappz
getappz enabled auto-merge (squash) July 17, 2026 19:28
@getappz
getappz merged commit de005a9 into master Jul 17, 2026
16 checks passed
@getappz
getappz deleted the devin/1784314581-error-handling branch July 17, 2026 19:54
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