Skip to content

feat(desktop): rotating logs, Copy logs, and Reveal log file - #95

Open
leoisadev1 wants to merge 6 commits into
cnv-57-posthog-error-trackingfrom
cursor/desktop-logs-error-reports-3d84
Open

leoisadev1 wants to merge 6 commits into
cnv-57-posthog-error-trackingfrom
cursor/desktop-logs-error-reports-3d84

Conversation

@leoisadev1

@leoisadev1 leoisadev1 commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Relation to #82

This PR is stacked on #82 (cnv-57-posthog-error-tracking). #82 is still open, so this stays targeted there. After #82 merges, this PR will retarget to main and update from main.

#82 already adds PostHog $exception reporting for the desktop app, web Worker, and API, plus path/secret scrubbing and the shared telemetry opt-out. This PR does not add a second reporter, a Send report button, or another opt-out switch.

What this PR adds on top of #82:

Review follow-up

  • Paths, credentials and personal data are scrubbed before a line is stored in memory or written to convt.log.
  • Rotation closes the file handle, then renames, so it works on Windows.
  • Copy / Reveal tests open Settings taller so the macOS Finder block does not hide the buttons.
  • Path scrubbing now keeps spaces inside a folder name (Mac /Users, Windows C:\Users, Linux /home). A path such as a home folder with a space in a directory name is replaced as one <PATH>, so later words from that folder do not leak into logs or Copy logs.

Verification

  • PASS (local Linux): cargo test -p convt-app — 138 passed.
  • PASS (local Linux): cargo clippy -p convt-app --all-targets -- -D warnings
  • NOT CHECKED: launching the desktop app (needs explicit permission).
Open in Web Open in Cursor 

cursoragent and others added 2 commits October 7, 2026 22:11
Adds a persistent rotating convt.log next to PR #82's crash files,
Copy logs (scrubbed diagnostics bundle on the clipboard), and Reveal
log file in Settings. Failed conversions now include format ids on
the existing reporter. Resend only uploads crash-*.log so the new
app log is not treated as a panic.

Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
#82's scrubber treats dotted versions as filenames. OS version is
not a secret, so the bundle leaves it alone. report_error stays as
the generic wrapper for non-conversion errors.

Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
@leoisadev1
leoisadev1 marked this pull request as ready for review October 7, 2026 22:25

@devin-ai-integration devin-ai-integration 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.

Devin Review found 8 potential issues.

Devin Review

Comment thread crates/convt-app/src/logs.rs Outdated
Comment on lines +109 to +113
if handle.metadata()?.len() >= ROTATE_BYTES {
let rotated = path.with_extension("log.1");
let _ = std::fs::rename(path, rotated);
*handle = open_log(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.

🔴 Windows log grows without rotating

On Windows, write_rotated cannot rename the open log and ignores the failure. It reopens that same log, so every later rotation also fails and the file grows without limit.

Learn more

Rotation renames the active log once it reaches two megabytes. Windows does not permit renaming this file while the File handle remains open, and the result is ignored. Reopening the unchanged path leaves the same oversized log in place, so every subsequent write retries the failing rotation. An existing convt.log.1 also needs to be handled under Windows replacement rules.

Example: On Windows, convt.log reaches 2 MiB. The next write cannot rename its open handle; after another million log entries, the same file is still active instead of being capped by rotation.

Recommended fix: Drop or take the active File before renaming. Handle an existing rotated file on Windows, restore or reopen the active log if rotation fails, and test two consecutive rotations.

Devin Review


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

#[cfg(target_os = "macos")]
{
return home.map(|p| p.join("Library/Logs/convt"));
return home.map(|p| p.join("Library/Logs/Convt"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Pending macOS crash reports lose retries

After an upgrade on macOS, logs_dir points at Convt instead of the previous convt directory. resend scans only the new directory, leaving earlier unsent crashes unreported.

Learn more

The crash hook previously saved reports under ~/Library/Logs/convt. On case-sensitive macOS volumes, the new ~/Library/Logs/Convt directory is distinct. At startup, resend reads only the new location and does not discover pending reports in the former location. On a case-insensitive volume both names resolve to the same directory, so this specifically affects case-sensitive filesystems.

Example: A Mac with a case-sensitive volume has ~/Library/Logs/convt/crash-123-456.log without its .sent marker. After the upgrade, resend looks only under ~/Library/Logs/Convt, and that crash never retries.

Recommended fix: Migrate pending crash logs from the former directory or include the legacy macOS directory when scanning for unsent reports.

Devin Review


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

Comment thread crates/convt-app/src/logs.rs Outdated
Comment on lines +105 to +108
fn write_rotated(file: &mut Option<File>, path: &Path, line: &str) -> io::Result<()> {
let Some(handle) = file.as_mut() else {
return Ok(());
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Startup log failure persists all session

If open_log fails at startup, LogStore retains no file handle. write_rotated returns success for that state, so later writes never retry after the log folder becomes writable.

Learn more

The log store opens its file once during initialization. If the folder is temporarily unavailable, LogStore::open keeps None. Each later event enters write_rotated, which returns Ok(()) immediately for None, bypassing the retry in LogStore::push. Memory logs continue, but no persistent app log is written for the rest of the process.

Example: A configured log directory is unavailable at launch and becomes available a minute later. New warnings continue appearing on stderr but never appear in convt.log until restart.

Recommended fix: Attempt open_log(path) when the stored handle is None, or return an error from write_rotated for that case so the caller retries opening it.

Devin Review


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

Comment on lines +929 to +934
let _ = std::fs::create_dir_all(&dir);
#[cfg(not(test))]
cx.open_with_system(&dir);
#[cfg(test)]
self.revealed.push(dir);
self.logs_notice = Some("Opened the log folder.".into());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Missing log folder reports successful reveal

When create_dir_all fails, reveal_logs still attempts to open the missing folder and announces success. Users receive no indication that Reveal log file failed.

Learn more

The reveal action creates the configured log directory before passing it to the system file manager. A filesystem error is ignored and the action unconditionally records a successful notice. The Support section shows that notice as the action result.

Example: CONVT_LOG_DIR names a directory under a read-only mount. Creation fails, the file manager receives a nonexistent path, yet Settings shows “Opened the log folder.”

Recommended fix: Check the create_dir_all result before opening the path. Set logs_notice to the error and return early when the directory cannot be created.

Devin Review


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

Comment thread crates/convt-app/src/ui/tests.rs Outdated
Comment on lines +3793 to +3803
let (settings, _) = f.settings(SettingsTab::General, cx);
assert!(shown(cx, settings, "copy-logs"));
assert!(shown(cx, settings, "reveal-log-file"));
click(cx, settings, "copy-logs");
let text = clipboard_text(cx).expect("Copy logs wrote the clipboard");
assert!(text.starts_with("convt-app "), "{text}");
assert!(text.contains("os: "), "{text}");
assert!(text.contains("os_version: "), "{text}");
assert!(text.contains("arch: "), "{text}");
assert!(text.contains("license: licensed (desktop)\n"), "{text}");
assert!(text.contains("--- logs ---"), "{text}");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Clipboard test leaves log capture untested

The fixture never initializes logs::init or emits a trace event. This test verifies the bundle header but not captured log lines reaching the clipboard.

Devin Review


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

Comment on lines +235 to +239
}
#[cfg(target_os = "windows")]
{
"Windows".into()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Windows bundle omits release information

On Windows, os_version is always Windows, duplicating os. The diagnostics bundle cannot distinguish Windows releases.

Devin Review


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

Comment on lines +183 to +185
for line in input.lines {
out.push_str(&crash_report::scrub(line));
out.push('\n');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟨 Copied diagnostics can expose credentials

When a log line contains a credential without path characters, format_bundle copies it unchanged. scrub removes paths, not tokens, so sharing the advertised sanitized bundle can disclose the credential.

Devin Review


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

Comment on lines +143 to +148
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let line = String::from_utf8_lossy(buf)
.trim_end_matches(['\n', '\r'])
.to_string();
self.store.push(line);
Ok(buf.len())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟨 Persistent log retains unredacted trace data

When a trace contains a user path or credential, LogWriter writes it to disk without redaction. The rotating log retains that information even after the original request finishes.

Devin Review


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

@greptile-apps

greptile-apps Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Adds rotating logs and diagnostics bundle feature.

The PR appears safe to merge, with six non-blocking findings from earlier reviews still open.

Fix All in Claude CodeFindings

  1. P2 Disk logging never recovers ▶
  2. P2 Old crash reports stop retrying ▶
  3. P2 Reveal reports a false success ▶
  4. P2 Windows version is missing ▶
  5. P2 Saved logs lose their times ▶
  6. P2 Log source names disappear ▶

Summary

This PR adds rotating desktop logs and a Support section in Settings.

  • Copy logs places app details and recent scrubbed logs on the clipboard.
  • Reveal log file opens the log folder.
  • Failed conversion reports include format IDs, and crash retries skip the rotating app log.
  • The code is unchanged since the previous review. No new findings were accepted.
  • Desktop launch and visual checks were not run because this request did not grant permission.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Desktop log entry] --> B[Scrub paths and secrets]
  B --> C[Last 200 lines in memory]
  B --> D[Rotating convt.log]
  C --> E[Copy logs]
  F[App and license details] --> E
  E --> G[Clipboard]
  H[Reveal log file] --> I[Open log folder]
Loading

Reviews (5) · Last reviewed commit: "chore: retrigger CI after a flaky databa..." · Reviewed by Greptile

Comment thread crates/convt-app/src/logs.rs Outdated
Comment thread crates/convt-app/src/logs.rs Outdated
Comment on lines +106 to +108
let Some(handle) = file.as_mut() else {
return Ok(());
};

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 Disk logging never recovers

Once opening convt.log fails, disk logging cannot recover during that run. write_rotated returns success for a missing handle, so push never tries reopening it. Retry opening when the handle is missing so logging resumes after a temporary folder or storage failure.

Confidence: 5/5.

Fix in Claude Code

#[cfg(target_os = "macos")]
{
return home.map(|p| p.join("Library/Logs/convt"));
return home.map(|p| p.join("Library/Logs/Convt"));

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 Old crash reports stop retrying

If a macOS user has a case-sensitive volume, changing the folder from Library/Logs/convt to Library/Logs/Convt leaves pending crash reports behind. resend reads only the new folder. Migrate the old crash files and their .sent markers, or also check the old folder.

Confidence: 5/5.

Fix in Claude Code

cx.notify();
return;
};
let _ = std::fs::create_dir_all(&dir);

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 Reveal reports a false success

reveal_logs reports that the folder opened even when create_dir_all fails. For example, an unwritable CONVT_LOG_DIR produces a success notice for a folder that does not exist. Show the creation error and return before asking the OS to open it.

Confidence: 5/5.

Fix in Claude Code

Comment on lines +236 to +238
#[cfg(target_os = "windows")]
{
"Windows".into()

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 Windows version is missing

On Windows, os_version always returns Windows. The copied bundle therefore cannot distinguish Windows versions, despite including an os_version field. Read the installed version so support can identify version-specific failures.

Confidence: 5/5.

Fix in Claude Code

…rt clicks

Sanitize paths and secrets before they reach the in-memory ring or
convt.log. Close the file handle before rename so rotation works on
Windows. Open Settings taller in the Copy/Reveal tests so the Finder
block on macOS does not hide the buttons.

Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
if line.is_empty() {
return;
}
let line = sanitize(&line);

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 Saved logs lose their times

LogStore::push now passes the whole formatted entry through crash_report::scrub. That function treats words with short dotted suffixes as filenames, so a default tracing timestamp such as 2026-10-07T12:34:56.123456Z becomes <PATH> before it reaches memory or disk. Support can no longer tell when events happened or how far apart failures occurred. Keep the timestamp separate from the text that needs scrubbing.

Fix in Claude Code

Keep report_conversion format ids and clear automation clipboard
state on a failed job. Resolves the Settings/model conflict after
#82 merged main.

Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
Comment thread crates/convt-app/src/logs.rs
Comment on lines +188 to +189
} else if lower.starts_with("cvt_")
|| lower.starts_with("convt_")

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 Log source names disappear

The convt_ prefix check also removes tracing source names such as convt_app::ui and convt_core::registry. The default log layout includes these names, so ordinary app logs store <license-key> instead of the component that wrote them. This makes failures harder for support to trace. Match the actual key format, or keep tracing source names outside credential scrubbing.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

cursoragent and others added 2 commits October 8, 2026 00:25
The shared scrubber stopped at the first space, so names after that
in a home path leaked into logs and Copy logs. Consume the rest of
the path on Mac, Windows and Linux, with a test for each.

Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
The desktop jobs passed. Database integration tests failed on
billed_at and paid_at are not moved by a later snapshot after
Postgres dropped the test database mid-run. That suite is
unchanged by this PR.

Co-authored-by: Leo <leoisadev1@users.noreply.github.com>

This branch has not been deployed

No deployments
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