refactor(config): rewrite update_config through the locked handle - #257

Merged
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config
Apr 21, 2026
Merged

refactor(config): rewrite update_config through the locked handle#257
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config

Conversation

@sachiniyer

@sachiniyersachiniyer commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Why this is a refactor, not a bug fix

The linked bug claimed the shadowed handle drops early, releasing the flock mid-write. It does not — Rust shadowing keeps the old binding alive until end of scope. I verified with a subprocess flock -n test that the lock is still held after the shadow. The bug report's failing test most likely picked up the tiny artifact between function return (Drop releases the flock) and the main thread flipping its "in-update" flag to false, not a real mid-write unlock.

No behavior change; existing concurrent_update_config_does_not_corrupt still passes.

Test plan

  • cargo test --lib config::storage — 15 pass
  • cargo clippy -- -D warnings clean
  • cargo fmt --check clean

🤖 Generated with Claude Code


Open in Devin Review

`update_config` opened a second `File::create` handle solely to truncate
and write, which made `file.unlock()` act on a handle that was never
locked. The real lock release happened implicitly via `Drop` at end of
scope — functionally correct but confusing, and it made the code look
like it contained the race described in bug_e79362bd (github #229).
Rewind, truncate in place (`set_len(0)`), and write through the handle
that already holds the flock. `file.unlock()` now releases the lock
that was actually acquired. No behavior change; existing
`concurrent_update_config_does_not_corrupt` still passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

No issues found across 1 file

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment threadsrc/config/storage.rs

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.

🚩 save_config has the same pattern the PR fixes in update_config

The save_config function at src/config/storage.rs:65-73 uses File::create followed by lock_exclusive, which means data is written to the file after locking, but there's a window between File::create (which truncates the file) and lock_exclusive where another reader could see an empty/partial file. This is a pre-existing issue with a similar pattern to what the PR fixes in update_config. It's less critical here because save_config doesn't read-then-write (no TOCTOU), but a concurrent load_config (which doesn't acquire a lock) could read a truncated file during the window between File::create and write_all.

(Refers to lines 68-71)

Open in Devin Review

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

Comment threadsrc/config/storage.rs
Comment on lines +102 to +103
(&file).seek(SeekFrom::Start(0))?;
file.set_len(0)?;

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.

📝 Info: Old code silently released the exclusive lock before writing

The old code let file = File::create(&path)?; at line 98 shadowed the file variable, causing the original locked file descriptor to be dropped. This released the exclusive lock before the write, creating a TOCTOU race window where another process could interleave. The new code correctly reuses the same handle via seek + set_len, maintaining the lock throughout the entire read-modify-write cycle. This is the core correctness improvement of the PR.

Open in Devin Review

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

@sachiniyer
sachiniyer merged commit 7f61a89 into mainApr 21, 2026
13 checks passed
@sachiniyer
sachiniyer deleted the siyer/clarify-update-config branch April 21, 2026 23:10
sachiniyer added a commit that referenced this pull request Apr 23, 2026
## Summary
Patch release rolling up the seven fixes merged since 0.2.1:
- fix(bugs): print empty-results hint when `--vulns` alone yields no
matches (#264)
- fix(datetime): floor sub-second negative timestamps instead of
snapping to epoch (#262)
- chore(deps): bump rustls-webpki to 0.103.13 for RUSTSEC-2026-0104
(#263)
- fix(repos): normalize whitespace in repo identifiers before lookup
(#261)
- fix(git): parse GitHub remotes with embedded http(s) credentials
(#260)
- fix(auth): redirect browser and surface OAuth errors on PKCE callback
failure (#258)
- refactor(config): rewrite `update_config` through the locked handle
(#257)
On merge, the release workflow will tag `v0.2.2` and publish platform
artifacts via cargo-dist.
## Test plan
- [x] `cargo build` succeeds with version 0.2.2
- [ ] Tag `v0.2.2` is created on merge and release workflow publishes
artifacts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/265"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sachiniyer added a commit that referenced this pull request May 16, 2026
## Summary
Expands `REVIEW.md` from a single rule (no `pub(super)`) into a
comprehensive set of review guidelines extracted from the full commit
history (259 commits). Each section documents a convention that has been
repeatedly enforced through past PRs and/or the lint configuration in
`Cargo.toml` / `lib.rs`.
New sections cover: visibility, error handling, type safety & casts,
imports & paths, generated code & OpenAPI, output format discipline,
config file handling, concurrency, testing, CLI argument design, code
organization, and commit messages.
Documentation-only change — no code modified.
## Review & Testing Checklist for Human
- [ ] **Accuracy of each convention**: These were inferred from commit
history by an AI, not dictated by a human. Read through each bullet and
verify it matches your actual intent — some rules may be stated too
broadly or too narrowly (e.g., is `console::Term` truly the only
acceptable output mechanism? Is `const fn` always preferred, or only in
specific contexts?).
- [ ] **Completeness**: Are there important conventions missing that
should be documented? For example, dependency management policies, PR
size expectations, or branch naming.
- [ ] **Commit message section**: The final section prescribes
conventional commits (`type(scope): description`). Confirm this is a
convention you want enforced — the commit history shows mixed adherence
(earlier commits don't follow it).
### Notes
- The original `pub(super)` rule is preserved and expanded with a
visibility-narrowing guideline derived from PRs #180 and #190.
- Guidelines about config handling (atomic writes, comment preservation,
locking) reflect the significant effort invested in PRs #131, #220,
#257, and #266.
- The lint-related sections mirror what's already enforced in
`Cargo.toml` `[lints.clippy]` and `[lints.rust]` — making them explicit
here helps reviewers who don't check the lint config.
Link to Devin session:
https://app.devin.ai/sessions/67611649d13b486dbe1ebf06c87acc47
Requested by: @sachiniyer
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/297"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Expanded and slimmed `REVIEW.md` into a guide for human-judged
conventions only; lint-enforced rules are omitted. Covers visibility
choices, error context with `anyhow::Result`, strict `--format json`,
optional `repo` arg with git-remote fallback, generated code via
`progenitor` and `cargo xtask` updates to `openapi.json`/help, config
writes via `update_config`, and conventional commits.
- **Migration**
- No migration needed; confirm the conventions match current
expectations.
<sup>Written for commit 74c602d.
Summary will update on new commits. <a
href="https://cubic.dev/pr/usedetail/cli/pull/297?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Sachin Iyer <siyer@detail.dev>
Sign up for freeto 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

@sachiniyer
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

refactor(config): rewrite update_config through the locked handle - #257

Merged
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config
Apr 21, 2026
Merged

refactor(config): rewrite update_config through the locked handle#257
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config

Conversation

@sachiniyer

@sachiniyersachiniyer commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Why this is a refactor, not a bug fix

The linked bug claimed the shadowed handle drops early, releasing the flock mid-write. It does not — Rust shadowing keeps the old binding alive until end of scope. I verified with a subprocess flock -n test that the lock is still held after the shadow. The bug report's failing test most likely picked up the tiny artifact between function return (Drop releases the flock) and the main thread flipping its "in-update" flag to false, not a real mid-write unlock.

No behavior change; existing concurrent_update_config_does_not_corrupt still passes.

Test plan

  • cargo test --lib config::storage — 15 pass
  • cargo clippy -- -D warnings clean
  • cargo fmt --check clean

🤖 Generated with Claude Code


Open in Devin Review

`update_config` opened a second `File::create` handle solely to truncate
and write, which made `file.unlock()` act on a handle that was never
locked. The real lock release happened implicitly via `Drop` at end of
scope — functionally correct but confusing, and it made the code look
like it contained the race described in bug_e79362bd (github #229).
Rewind, truncate in place (`set_len(0)`), and write through the handle
that already holds the flock. `file.unlock()` now releases the lock
that was actually acquired. No behavior change; existing
`concurrent_update_config_does_not_corrupt` still passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

No issues found across 1 file

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment threadsrc/config/storage.rs

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.

🚩 save_config has the same pattern the PR fixes in update_config

The save_config function at src/config/storage.rs:65-73 uses File::create followed by lock_exclusive, which means data is written to the file after locking, but there's a window between File::create (which truncates the file) and lock_exclusive where another reader could see an empty/partial file. This is a pre-existing issue with a similar pattern to what the PR fixes in update_config. It's less critical here because save_config doesn't read-then-write (no TOCTOU), but a concurrent load_config (which doesn't acquire a lock) could read a truncated file during the window between File::create and write_all.

(Refers to lines 68-71)

Open in Devin Review

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

Comment threadsrc/config/storage.rs
Comment on lines +102 to +103
(&file).seek(SeekFrom::Start(0))?;
file.set_len(0)?;

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.

📝 Info: Old code silently released the exclusive lock before writing

The old code let file = File::create(&path)?; at line 98 shadowed the file variable, causing the original locked file descriptor to be dropped. This released the exclusive lock before the write, creating a TOCTOU race window where another process could interleave. The new code correctly reuses the same handle via seek + set_len, maintaining the lock throughout the entire read-modify-write cycle. This is the core correctness improvement of the PR.

Open in Devin Review

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

@sachiniyer
sachiniyer merged commit 7f61a89 into mainApr 21, 2026
13 checks passed
@sachiniyer
sachiniyer deleted the siyer/clarify-update-config branch April 21, 2026 23:10
sachiniyer added a commit that referenced this pull request Apr 23, 2026
## Summary
Patch release rolling up the seven fixes merged since 0.2.1:
- fix(bugs): print empty-results hint when `--vulns` alone yields no
matches (#264)
- fix(datetime): floor sub-second negative timestamps instead of
snapping to epoch (#262)
- chore(deps): bump rustls-webpki to 0.103.13 for RUSTSEC-2026-0104
(#263)
- fix(repos): normalize whitespace in repo identifiers before lookup
(#261)
- fix(git): parse GitHub remotes with embedded http(s) credentials
(#260)
- fix(auth): redirect browser and surface OAuth errors on PKCE callback
failure (#258)
- refactor(config): rewrite `update_config` through the locked handle
(#257)
On merge, the release workflow will tag `v0.2.2` and publish platform
artifacts via cargo-dist.
## Test plan
- [x] `cargo build` succeeds with version 0.2.2
- [ ] Tag `v0.2.2` is created on merge and release workflow publishes
artifacts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/265"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sachiniyer added a commit that referenced this pull request May 16, 2026
## Summary
Expands `REVIEW.md` from a single rule (no `pub(super)`) into a
comprehensive set of review guidelines extracted from the full commit
history (259 commits). Each section documents a convention that has been
repeatedly enforced through past PRs and/or the lint configuration in
`Cargo.toml` / `lib.rs`.
New sections cover: visibility, error handling, type safety & casts,
imports & paths, generated code & OpenAPI, output format discipline,
config file handling, concurrency, testing, CLI argument design, code
organization, and commit messages.
Documentation-only change — no code modified.
## Review & Testing Checklist for Human
- [ ] **Accuracy of each convention**: These were inferred from commit
history by an AI, not dictated by a human. Read through each bullet and
verify it matches your actual intent — some rules may be stated too
broadly or too narrowly (e.g., is `console::Term` truly the only
acceptable output mechanism? Is `const fn` always preferred, or only in
specific contexts?).
- [ ] **Completeness**: Are there important conventions missing that
should be documented? For example, dependency management policies, PR
size expectations, or branch naming.
- [ ] **Commit message section**: The final section prescribes
conventional commits (`type(scope): description`). Confirm this is a
convention you want enforced — the commit history shows mixed adherence
(earlier commits don't follow it).
### Notes
- The original `pub(super)` rule is preserved and expanded with a
visibility-narrowing guideline derived from PRs #180 and #190.
- Guidelines about config handling (atomic writes, comment preservation,
locking) reflect the significant effort invested in PRs #131, #220,
#257, and #266.
- The lint-related sections mirror what's already enforced in
`Cargo.toml` `[lints.clippy]` and `[lints.rust]` — making them explicit
here helps reviewers who don't check the lint config.
Link to Devin session:
https://app.devin.ai/sessions/67611649d13b486dbe1ebf06c87acc47
Requested by: @sachiniyer
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/297"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Expanded and slimmed `REVIEW.md` into a guide for human-judged
conventions only; lint-enforced rules are omitted. Covers visibility
choices, error context with `anyhow::Result`, strict `--format json`,
optional `repo` arg with git-remote fallback, generated code via
`progenitor` and `cargo xtask` updates to `openapi.json`/help, config
writes via `update_config`, and conventional commits.
- **Migration**
- No migration needed; confirm the conventions match current
expectations.
<sup>Written for commit 74c602d.
Summary will update on new commits. <a
href="https://cubic.dev/pr/usedetail/cli/pull/297?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Sachin Iyer <siyer@detail.dev>
Sign up for freeto 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

@sachiniyer
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor(config): rewrite update_config through the locked handle - #257

Merged
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config
Apr 21, 2026
Merged

refactor(config): rewrite update_config through the locked handle#257
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config

Conversation

@sachiniyer

@sachiniyersachiniyer commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Why this is a refactor, not a bug fix

The linked bug claimed the shadowed handle drops early, releasing the flock mid-write. It does not — Rust shadowing keeps the old binding alive until end of scope. I verified with a subprocess flock -n test that the lock is still held after the shadow. The bug report's failing test most likely picked up the tiny artifact between function return (Drop releases the flock) and the main thread flipping its "in-update" flag to false, not a real mid-write unlock.

No behavior change; existing concurrent_update_config_does_not_corrupt still passes.

Test plan

  • cargo test --lib config::storage — 15 pass
  • cargo clippy -- -D warnings clean
  • cargo fmt --check clean

🤖 Generated with Claude Code


Open in Devin Review

`update_config` opened a second `File::create` handle solely to truncate
and write, which made `file.unlock()` act on a handle that was never
locked. The real lock release happened implicitly via `Drop` at end of
scope — functionally correct but confusing, and it made the code look
like it contained the race described in bug_e79362bd (github #229).
Rewind, truncate in place (`set_len(0)`), and write through the handle
that already holds the flock. `file.unlock()` now releases the lock
that was actually acquired. No behavior change; existing
`concurrent_update_config_does_not_corrupt` still passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

No issues found across 1 file

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment threadsrc/config/storage.rs

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.

🚩 save_config has the same pattern the PR fixes in update_config

The save_config function at src/config/storage.rs:65-73 uses File::create followed by lock_exclusive, which means data is written to the file after locking, but there's a window between File::create (which truncates the file) and lock_exclusive where another reader could see an empty/partial file. This is a pre-existing issue with a similar pattern to what the PR fixes in update_config. It's less critical here because save_config doesn't read-then-write (no TOCTOU), but a concurrent load_config (which doesn't acquire a lock) could read a truncated file during the window between File::create and write_all.

(Refers to lines 68-71)

Open in Devin Review

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

Comment threadsrc/config/storage.rs
Comment on lines +102 to +103
(&file).seek(SeekFrom::Start(0))?;
file.set_len(0)?;

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.

📝 Info: Old code silently released the exclusive lock before writing

The old code let file = File::create(&path)?; at line 98 shadowed the file variable, causing the original locked file descriptor to be dropped. This released the exclusive lock before the write, creating a TOCTOU race window where another process could interleave. The new code correctly reuses the same handle via seek + set_len, maintaining the lock throughout the entire read-modify-write cycle. This is the core correctness improvement of the PR.

Open in Devin Review

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

@sachiniyer
sachiniyer merged commit 7f61a89 into mainApr 21, 2026
13 checks passed
@sachiniyer
sachiniyer deleted the siyer/clarify-update-config branch April 21, 2026 23:10
sachiniyer added a commit that referenced this pull request Apr 23, 2026
## Summary
Patch release rolling up the seven fixes merged since 0.2.1:
- fix(bugs): print empty-results hint when `--vulns` alone yields no
matches (#264)
- fix(datetime): floor sub-second negative timestamps instead of
snapping to epoch (#262)
- chore(deps): bump rustls-webpki to 0.103.13 for RUSTSEC-2026-0104
(#263)
- fix(repos): normalize whitespace in repo identifiers before lookup
(#261)
- fix(git): parse GitHub remotes with embedded http(s) credentials
(#260)
- fix(auth): redirect browser and surface OAuth errors on PKCE callback
failure (#258)
- refactor(config): rewrite `update_config` through the locked handle
(#257)
On merge, the release workflow will tag `v0.2.2` and publish platform
artifacts via cargo-dist.
## Test plan
- [x] `cargo build` succeeds with version 0.2.2
- [ ] Tag `v0.2.2` is created on merge and release workflow publishes
artifacts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/265"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sachiniyer added a commit that referenced this pull request May 16, 2026
## Summary
Expands `REVIEW.md` from a single rule (no `pub(super)`) into a
comprehensive set of review guidelines extracted from the full commit
history (259 commits). Each section documents a convention that has been
repeatedly enforced through past PRs and/or the lint configuration in
`Cargo.toml` / `lib.rs`.
New sections cover: visibility, error handling, type safety & casts,
imports & paths, generated code & OpenAPI, output format discipline,
config file handling, concurrency, testing, CLI argument design, code
organization, and commit messages.
Documentation-only change — no code modified.
## Review & Testing Checklist for Human
- [ ] **Accuracy of each convention**: These were inferred from commit
history by an AI, not dictated by a human. Read through each bullet and
verify it matches your actual intent — some rules may be stated too
broadly or too narrowly (e.g., is `console::Term` truly the only
acceptable output mechanism? Is `const fn` always preferred, or only in
specific contexts?).
- [ ] **Completeness**: Are there important conventions missing that
should be documented? For example, dependency management policies, PR
size expectations, or branch naming.
- [ ] **Commit message section**: The final section prescribes
conventional commits (`type(scope): description`). Confirm this is a
convention you want enforced — the commit history shows mixed adherence
(earlier commits don't follow it).
### Notes
- The original `pub(super)` rule is preserved and expanded with a
visibility-narrowing guideline derived from PRs #180 and #190.
- Guidelines about config handling (atomic writes, comment preservation,
locking) reflect the significant effort invested in PRs #131, #220,
#257, and #266.
- The lint-related sections mirror what's already enforced in
`Cargo.toml` `[lints.clippy]` and `[lints.rust]` — making them explicit
here helps reviewers who don't check the lint config.
Link to Devin session:
https://app.devin.ai/sessions/67611649d13b486dbe1ebf06c87acc47
Requested by: @sachiniyer
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/297"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Expanded and slimmed `REVIEW.md` into a guide for human-judged
conventions only; lint-enforced rules are omitted. Covers visibility
choices, error context with `anyhow::Result`, strict `--format json`,
optional `repo` arg with git-remote fallback, generated code via
`progenitor` and `cargo xtask` updates to `openapi.json`/help, config
writes via `update_config`, and conventional commits.
- **Migration**
- No migration needed; confirm the conventions match current
expectations.
<sup>Written for commit 74c602d.
Summary will update on new commits. <a
href="https://cubic.dev/pr/usedetail/cli/pull/297?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Sachin Iyer <siyer@detail.dev>
Sign up for freeto 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

@sachiniyer
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor(config): rewrite update_config through the locked handle - #257

Merged
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config
Apr 21, 2026
Merged

refactor(config): rewrite update_config through the locked handle#257
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config

Conversation

@sachiniyer

@sachiniyersachiniyer commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Why this is a refactor, not a bug fix

The linked bug claimed the shadowed handle drops early, releasing the flock mid-write. It does not — Rust shadowing keeps the old binding alive until end of scope. I verified with a subprocess flock -n test that the lock is still held after the shadow. The bug report's failing test most likely picked up the tiny artifact between function return (Drop releases the flock) and the main thread flipping its "in-update" flag to false, not a real mid-write unlock.

No behavior change; existing concurrent_update_config_does_not_corrupt still passes.

Test plan

  • cargo test --lib config::storage — 15 pass
  • cargo clippy -- -D warnings clean
  • cargo fmt --check clean

🤖 Generated with Claude Code


Open in Devin Review

`update_config` opened a second `File::create` handle solely to truncate
and write, which made `file.unlock()` act on a handle that was never
locked. The real lock release happened implicitly via `Drop` at end of
scope — functionally correct but confusing, and it made the code look
like it contained the race described in bug_e79362bd (github #229).
Rewind, truncate in place (`set_len(0)`), and write through the handle
that already holds the flock. `file.unlock()` now releases the lock
that was actually acquired. No behavior change; existing
`concurrent_update_config_does_not_corrupt` still passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

No issues found across 1 file

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment threadsrc/config/storage.rs

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.

🚩 save_config has the same pattern the PR fixes in update_config

The save_config function at src/config/storage.rs:65-73 uses File::create followed by lock_exclusive, which means data is written to the file after locking, but there's a window between File::create (which truncates the file) and lock_exclusive where another reader could see an empty/partial file. This is a pre-existing issue with a similar pattern to what the PR fixes in update_config. It's less critical here because save_config doesn't read-then-write (no TOCTOU), but a concurrent load_config (which doesn't acquire a lock) could read a truncated file during the window between File::create and write_all.

(Refers to lines 68-71)

Open in Devin Review

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

Comment threadsrc/config/storage.rs
Comment on lines +102 to +103
(&file).seek(SeekFrom::Start(0))?;
file.set_len(0)?;

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.

📝 Info: Old code silently released the exclusive lock before writing

The old code let file = File::create(&path)?; at line 98 shadowed the file variable, causing the original locked file descriptor to be dropped. This released the exclusive lock before the write, creating a TOCTOU race window where another process could interleave. The new code correctly reuses the same handle via seek + set_len, maintaining the lock throughout the entire read-modify-write cycle. This is the core correctness improvement of the PR.

Open in Devin Review

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

@sachiniyer
sachiniyer merged commit 7f61a89 into mainApr 21, 2026
13 checks passed
@sachiniyer
sachiniyer deleted the siyer/clarify-update-config branch April 21, 2026 23:10
sachiniyer added a commit that referenced this pull request Apr 23, 2026
## Summary
Patch release rolling up the seven fixes merged since 0.2.1:
- fix(bugs): print empty-results hint when `--vulns` alone yields no
matches (#264)
- fix(datetime): floor sub-second negative timestamps instead of
snapping to epoch (#262)
- chore(deps): bump rustls-webpki to 0.103.13 for RUSTSEC-2026-0104
(#263)
- fix(repos): normalize whitespace in repo identifiers before lookup
(#261)
- fix(git): parse GitHub remotes with embedded http(s) credentials
(#260)
- fix(auth): redirect browser and surface OAuth errors on PKCE callback
failure (#258)
- refactor(config): rewrite `update_config` through the locked handle
(#257)
On merge, the release workflow will tag `v0.2.2` and publish platform
artifacts via cargo-dist.
## Test plan
- [x] `cargo build` succeeds with version 0.2.2
- [ ] Tag `v0.2.2` is created on merge and release workflow publishes
artifacts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/265"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sachiniyer added a commit that referenced this pull request May 16, 2026
## Summary
Expands `REVIEW.md` from a single rule (no `pub(super)`) into a
comprehensive set of review guidelines extracted from the full commit
history (259 commits). Each section documents a convention that has been
repeatedly enforced through past PRs and/or the lint configuration in
`Cargo.toml` / `lib.rs`.
New sections cover: visibility, error handling, type safety & casts,
imports & paths, generated code & OpenAPI, output format discipline,
config file handling, concurrency, testing, CLI argument design, code
organization, and commit messages.
Documentation-only change — no code modified.
## Review & Testing Checklist for Human
- [ ] **Accuracy of each convention**: These were inferred from commit
history by an AI, not dictated by a human. Read through each bullet and
verify it matches your actual intent — some rules may be stated too
broadly or too narrowly (e.g., is `console::Term` truly the only
acceptable output mechanism? Is `const fn` always preferred, or only in
specific contexts?).
- [ ] **Completeness**: Are there important conventions missing that
should be documented? For example, dependency management policies, PR
size expectations, or branch naming.
- [ ] **Commit message section**: The final section prescribes
conventional commits (`type(scope): description`). Confirm this is a
convention you want enforced — the commit history shows mixed adherence
(earlier commits don't follow it).
### Notes
- The original `pub(super)` rule is preserved and expanded with a
visibility-narrowing guideline derived from PRs #180 and #190.
- Guidelines about config handling (atomic writes, comment preservation,
locking) reflect the significant effort invested in PRs #131, #220,
#257, and #266.
- The lint-related sections mirror what's already enforced in
`Cargo.toml` `[lints.clippy]` and `[lints.rust]` — making them explicit
here helps reviewers who don't check the lint config.
Link to Devin session:
https://app.devin.ai/sessions/67611649d13b486dbe1ebf06c87acc47
Requested by: @sachiniyer
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/297"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Expanded and slimmed `REVIEW.md` into a guide for human-judged
conventions only; lint-enforced rules are omitted. Covers visibility
choices, error context with `anyhow::Result`, strict `--format json`,
optional `repo` arg with git-remote fallback, generated code via
`progenitor` and `cargo xtask` updates to `openapi.json`/help, config
writes via `update_config`, and conventional commits.
- **Migration**
- No migration needed; confirm the conventions match current
expectations.
<sup>Written for commit 74c602d.
Summary will update on new commits. <a
href="https://cubic.dev/pr/usedetail/cli/pull/297?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Sachin Iyer <siyer@detail.dev>
Sign up for freeto 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

@sachiniyer
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

refactor(config): rewrite update_config through the locked handle - #257

Merged
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config
Apr 21, 2026
Merged

refactor(config): rewrite update_config through the locked handle#257
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config

Conversation

@sachiniyer

@sachiniyersachiniyer commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Why this is a refactor, not a bug fix

The linked bug claimed the shadowed handle drops early, releasing the flock mid-write. It does not — Rust shadowing keeps the old binding alive until end of scope. I verified with a subprocess flock -n test that the lock is still held after the shadow. The bug report's failing test most likely picked up the tiny artifact between function return (Drop releases the flock) and the main thread flipping its "in-update" flag to false, not a real mid-write unlock.

No behavior change; existing concurrent_update_config_does_not_corrupt still passes.

Test plan

  • cargo test --lib config::storage — 15 pass
  • cargo clippy -- -D warnings clean
  • cargo fmt --check clean

🤖 Generated with Claude Code


Open in Devin Review

`update_config` opened a second `File::create` handle solely to truncate
and write, which made `file.unlock()` act on a handle that was never
locked. The real lock release happened implicitly via `Drop` at end of
scope — functionally correct but confusing, and it made the code look
like it contained the race described in bug_e79362bd (github #229).
Rewind, truncate in place (`set_len(0)`), and write through the handle
that already holds the flock. `file.unlock()` now releases the lock
that was actually acquired. No behavior change; existing
`concurrent_update_config_does_not_corrupt` still passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

No issues found across 1 file

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment threadsrc/config/storage.rs

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.

🚩 save_config has the same pattern the PR fixes in update_config

The save_config function at src/config/storage.rs:65-73 uses File::create followed by lock_exclusive, which means data is written to the file after locking, but there's a window between File::create (which truncates the file) and lock_exclusive where another reader could see an empty/partial file. This is a pre-existing issue with a similar pattern to what the PR fixes in update_config. It's less critical here because save_config doesn't read-then-write (no TOCTOU), but a concurrent load_config (which doesn't acquire a lock) could read a truncated file during the window between File::create and write_all.

(Refers to lines 68-71)

Open in Devin Review

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

Comment threadsrc/config/storage.rs
Comment on lines +102 to +103
(&file).seek(SeekFrom::Start(0))?;
file.set_len(0)?;

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.

📝 Info: Old code silently released the exclusive lock before writing

The old code let file = File::create(&path)?; at line 98 shadowed the file variable, causing the original locked file descriptor to be dropped. This released the exclusive lock before the write, creating a TOCTOU race window where another process could interleave. The new code correctly reuses the same handle via seek + set_len, maintaining the lock throughout the entire read-modify-write cycle. This is the core correctness improvement of the PR.

Open in Devin Review

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

@sachiniyer
sachiniyer merged commit 7f61a89 into mainApr 21, 2026
13 checks passed
@sachiniyer
sachiniyer deleted the siyer/clarify-update-config branch April 21, 2026 23:10
sachiniyer added a commit that referenced this pull request Apr 23, 2026
## Summary
Patch release rolling up the seven fixes merged since 0.2.1:
- fix(bugs): print empty-results hint when `--vulns` alone yields no
matches (#264)
- fix(datetime): floor sub-second negative timestamps instead of
snapping to epoch (#262)
- chore(deps): bump rustls-webpki to 0.103.13 for RUSTSEC-2026-0104
(#263)
- fix(repos): normalize whitespace in repo identifiers before lookup
(#261)
- fix(git): parse GitHub remotes with embedded http(s) credentials
(#260)
- fix(auth): redirect browser and surface OAuth errors on PKCE callback
failure (#258)
- refactor(config): rewrite `update_config` through the locked handle
(#257)
On merge, the release workflow will tag `v0.2.2` and publish platform
artifacts via cargo-dist.
## Test plan
- [x] `cargo build` succeeds with version 0.2.2
- [ ] Tag `v0.2.2` is created on merge and release workflow publishes
artifacts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/265"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sachiniyer added a commit that referenced this pull request May 16, 2026
## Summary
Expands `REVIEW.md` from a single rule (no `pub(super)`) into a
comprehensive set of review guidelines extracted from the full commit
history (259 commits). Each section documents a convention that has been
repeatedly enforced through past PRs and/or the lint configuration in
`Cargo.toml` / `lib.rs`.
New sections cover: visibility, error handling, type safety & casts,
imports & paths, generated code & OpenAPI, output format discipline,
config file handling, concurrency, testing, CLI argument design, code
organization, and commit messages.
Documentation-only change — no code modified.
## Review & Testing Checklist for Human
- [ ] **Accuracy of each convention**: These were inferred from commit
history by an AI, not dictated by a human. Read through each bullet and
verify it matches your actual intent — some rules may be stated too
broadly or too narrowly (e.g., is `console::Term` truly the only
acceptable output mechanism? Is `const fn` always preferred, or only in
specific contexts?).
- [ ] **Completeness**: Are there important conventions missing that
should be documented? For example, dependency management policies, PR
size expectations, or branch naming.
- [ ] **Commit message section**: The final section prescribes
conventional commits (`type(scope): description`). Confirm this is a
convention you want enforced — the commit history shows mixed adherence
(earlier commits don't follow it).
### Notes
- The original `pub(super)` rule is preserved and expanded with a
visibility-narrowing guideline derived from PRs #180 and #190.
- Guidelines about config handling (atomic writes, comment preservation,
locking) reflect the significant effort invested in PRs #131, #220,
#257, and #266.
- The lint-related sections mirror what's already enforced in
`Cargo.toml` `[lints.clippy]` and `[lints.rust]` — making them explicit
here helps reviewers who don't check the lint config.
Link to Devin session:
https://app.devin.ai/sessions/67611649d13b486dbe1ebf06c87acc47
Requested by: @sachiniyer
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/297"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Expanded and slimmed `REVIEW.md` into a guide for human-judged
conventions only; lint-enforced rules are omitted. Covers visibility
choices, error context with `anyhow::Result`, strict `--format json`,
optional `repo` arg with git-remote fallback, generated code via
`progenitor` and `cargo xtask` updates to `openapi.json`/help, config
writes via `update_config`, and conventional commits.
- **Migration**
- No migration needed; confirm the conventions match current
expectations.
<sup>Written for commit 74c602d.
Summary will update on new commits. <a
href="https://cubic.dev/pr/usedetail/cli/pull/297?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Sachin Iyer <siyer@detail.dev>
Sign up for freeto 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

@sachiniyer
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor(config): rewrite update_config through the locked handle - #257

Merged
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config
Apr 21, 2026
Merged

refactor(config): rewrite update_config through the locked handle#257
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config

Conversation

@sachiniyer

@sachiniyersachiniyer commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Why this is a refactor, not a bug fix

The linked bug claimed the shadowed handle drops early, releasing the flock mid-write. It does not — Rust shadowing keeps the old binding alive until end of scope. I verified with a subprocess flock -n test that the lock is still held after the shadow. The bug report's failing test most likely picked up the tiny artifact between function return (Drop releases the flock) and the main thread flipping its "in-update" flag to false, not a real mid-write unlock.

No behavior change; existing concurrent_update_config_does_not_corrupt still passes.

Test plan

  • cargo test --lib config::storage — 15 pass
  • cargo clippy -- -D warnings clean
  • cargo fmt --check clean

🤖 Generated with Claude Code


Open in Devin Review

`update_config` opened a second `File::create` handle solely to truncate
and write, which made `file.unlock()` act on a handle that was never
locked. The real lock release happened implicitly via `Drop` at end of
scope — functionally correct but confusing, and it made the code look
like it contained the race described in bug_e79362bd (github #229).
Rewind, truncate in place (`set_len(0)`), and write through the handle
that already holds the flock. `file.unlock()` now releases the lock
that was actually acquired. No behavior change; existing
`concurrent_update_config_does_not_corrupt` still passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

No issues found across 1 file

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment threadsrc/config/storage.rs

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.

🚩 save_config has the same pattern the PR fixes in update_config

The save_config function at src/config/storage.rs:65-73 uses File::create followed by lock_exclusive, which means data is written to the file after locking, but there's a window between File::create (which truncates the file) and lock_exclusive where another reader could see an empty/partial file. This is a pre-existing issue with a similar pattern to what the PR fixes in update_config. It's less critical here because save_config doesn't read-then-write (no TOCTOU), but a concurrent load_config (which doesn't acquire a lock) could read a truncated file during the window between File::create and write_all.

(Refers to lines 68-71)

Open in Devin Review

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

Comment threadsrc/config/storage.rs
Comment on lines +102 to +103
(&file).seek(SeekFrom::Start(0))?;
file.set_len(0)?;

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.

📝 Info: Old code silently released the exclusive lock before writing

The old code let file = File::create(&path)?; at line 98 shadowed the file variable, causing the original locked file descriptor to be dropped. This released the exclusive lock before the write, creating a TOCTOU race window where another process could interleave. The new code correctly reuses the same handle via seek + set_len, maintaining the lock throughout the entire read-modify-write cycle. This is the core correctness improvement of the PR.

Open in Devin Review

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

@sachiniyer
sachiniyer merged commit 7f61a89 into mainApr 21, 2026
13 checks passed
@sachiniyer
sachiniyer deleted the siyer/clarify-update-config branch April 21, 2026 23:10
sachiniyer added a commit that referenced this pull request Apr 23, 2026
## Summary
Patch release rolling up the seven fixes merged since 0.2.1:
- fix(bugs): print empty-results hint when `--vulns` alone yields no
matches (#264)
- fix(datetime): floor sub-second negative timestamps instead of
snapping to epoch (#262)
- chore(deps): bump rustls-webpki to 0.103.13 for RUSTSEC-2026-0104
(#263)
- fix(repos): normalize whitespace in repo identifiers before lookup
(#261)
- fix(git): parse GitHub remotes with embedded http(s) credentials
(#260)
- fix(auth): redirect browser and surface OAuth errors on PKCE callback
failure (#258)
- refactor(config): rewrite `update_config` through the locked handle
(#257)
On merge, the release workflow will tag `v0.2.2` and publish platform
artifacts via cargo-dist.
## Test plan
- [x] `cargo build` succeeds with version 0.2.2
- [ ] Tag `v0.2.2` is created on merge and release workflow publishes
artifacts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/265"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sachiniyer added a commit that referenced this pull request May 16, 2026
## Summary
Expands `REVIEW.md` from a single rule (no `pub(super)`) into a
comprehensive set of review guidelines extracted from the full commit
history (259 commits). Each section documents a convention that has been
repeatedly enforced through past PRs and/or the lint configuration in
`Cargo.toml` / `lib.rs`.
New sections cover: visibility, error handling, type safety & casts,
imports & paths, generated code & OpenAPI, output format discipline,
config file handling, concurrency, testing, CLI argument design, code
organization, and commit messages.
Documentation-only change — no code modified.
## Review & Testing Checklist for Human
- [ ] **Accuracy of each convention**: These were inferred from commit
history by an AI, not dictated by a human. Read through each bullet and
verify it matches your actual intent — some rules may be stated too
broadly or too narrowly (e.g., is `console::Term` truly the only
acceptable output mechanism? Is `const fn` always preferred, or only in
specific contexts?).
- [ ] **Completeness**: Are there important conventions missing that
should be documented? For example, dependency management policies, PR
size expectations, or branch naming.
- [ ] **Commit message section**: The final section prescribes
conventional commits (`type(scope): description`). Confirm this is a
convention you want enforced — the commit history shows mixed adherence
(earlier commits don't follow it).
### Notes
- The original `pub(super)` rule is preserved and expanded with a
visibility-narrowing guideline derived from PRs #180 and #190.
- Guidelines about config handling (atomic writes, comment preservation,
locking) reflect the significant effort invested in PRs #131, #220,
#257, and #266.
- The lint-related sections mirror what's already enforced in
`Cargo.toml` `[lints.clippy]` and `[lints.rust]` — making them explicit
here helps reviewers who don't check the lint config.
Link to Devin session:
https://app.devin.ai/sessions/67611649d13b486dbe1ebf06c87acc47
Requested by: @sachiniyer
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/297"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Expanded and slimmed `REVIEW.md` into a guide for human-judged
conventions only; lint-enforced rules are omitted. Covers visibility
choices, error context with `anyhow::Result`, strict `--format json`,
optional `repo` arg with git-remote fallback, generated code via
`progenitor` and `cargo xtask` updates to `openapi.json`/help, config
writes via `update_config`, and conventional commits.
- **Migration**
- No migration needed; confirm the conventions match current
expectations.
<sup>Written for commit 74c602d.
Summary will update on new commits. <a
href="https://cubic.dev/pr/usedetail/cli/pull/297?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Sachin Iyer <siyer@detail.dev>
Sign up for freeto 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

@sachiniyer
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor(config): rewrite update_config through the locked handle - #257

Merged
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config
Apr 21, 2026
Merged

refactor(config): rewrite update_config through the locked handle#257
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config

Conversation

@sachiniyer

@sachiniyersachiniyer commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Why this is a refactor, not a bug fix

The linked bug claimed the shadowed handle drops early, releasing the flock mid-write. It does not — Rust shadowing keeps the old binding alive until end of scope. I verified with a subprocess flock -n test that the lock is still held after the shadow. The bug report's failing test most likely picked up the tiny artifact between function return (Drop releases the flock) and the main thread flipping its "in-update" flag to false, not a real mid-write unlock.

No behavior change; existing concurrent_update_config_does_not_corrupt still passes.

Test plan

  • cargo test --lib config::storage — 15 pass
  • cargo clippy -- -D warnings clean
  • cargo fmt --check clean

🤖 Generated with Claude Code


Open in Devin Review

`update_config` opened a second `File::create` handle solely to truncate
and write, which made `file.unlock()` act on a handle that was never
locked. The real lock release happened implicitly via `Drop` at end of
scope — functionally correct but confusing, and it made the code look
like it contained the race described in bug_e79362bd (github #229).
Rewind, truncate in place (`set_len(0)`), and write through the handle
that already holds the flock. `file.unlock()` now releases the lock
that was actually acquired. No behavior change; existing
`concurrent_update_config_does_not_corrupt` still passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

No issues found across 1 file

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment threadsrc/config/storage.rs

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.

🚩 save_config has the same pattern the PR fixes in update_config

The save_config function at src/config/storage.rs:65-73 uses File::create followed by lock_exclusive, which means data is written to the file after locking, but there's a window between File::create (which truncates the file) and lock_exclusive where another reader could see an empty/partial file. This is a pre-existing issue with a similar pattern to what the PR fixes in update_config. It's less critical here because save_config doesn't read-then-write (no TOCTOU), but a concurrent load_config (which doesn't acquire a lock) could read a truncated file during the window between File::create and write_all.

(Refers to lines 68-71)

Open in Devin Review

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

Comment threadsrc/config/storage.rs
Comment on lines +102 to +103
(&file).seek(SeekFrom::Start(0))?;
file.set_len(0)?;

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.

📝 Info: Old code silently released the exclusive lock before writing

The old code let file = File::create(&path)?; at line 98 shadowed the file variable, causing the original locked file descriptor to be dropped. This released the exclusive lock before the write, creating a TOCTOU race window where another process could interleave. The new code correctly reuses the same handle via seek + set_len, maintaining the lock throughout the entire read-modify-write cycle. This is the core correctness improvement of the PR.

Open in Devin Review

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

@sachiniyer
sachiniyer merged commit 7f61a89 into mainApr 21, 2026
13 checks passed
@sachiniyer
sachiniyer deleted the siyer/clarify-update-config branch April 21, 2026 23:10
sachiniyer added a commit that referenced this pull request Apr 23, 2026
## Summary
Patch release rolling up the seven fixes merged since 0.2.1:
- fix(bugs): print empty-results hint when `--vulns` alone yields no
matches (#264)
- fix(datetime): floor sub-second negative timestamps instead of
snapping to epoch (#262)
- chore(deps): bump rustls-webpki to 0.103.13 for RUSTSEC-2026-0104
(#263)
- fix(repos): normalize whitespace in repo identifiers before lookup
(#261)
- fix(git): parse GitHub remotes with embedded http(s) credentials
(#260)
- fix(auth): redirect browser and surface OAuth errors on PKCE callback
failure (#258)
- refactor(config): rewrite `update_config` through the locked handle
(#257)
On merge, the release workflow will tag `v0.2.2` and publish platform
artifacts via cargo-dist.
## Test plan
- [x] `cargo build` succeeds with version 0.2.2
- [ ] Tag `v0.2.2` is created on merge and release workflow publishes
artifacts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/265"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sachiniyer added a commit that referenced this pull request May 16, 2026
## Summary
Expands `REVIEW.md` from a single rule (no `pub(super)`) into a
comprehensive set of review guidelines extracted from the full commit
history (259 commits). Each section documents a convention that has been
repeatedly enforced through past PRs and/or the lint configuration in
`Cargo.toml` / `lib.rs`.
New sections cover: visibility, error handling, type safety & casts,
imports & paths, generated code & OpenAPI, output format discipline,
config file handling, concurrency, testing, CLI argument design, code
organization, and commit messages.
Documentation-only change — no code modified.
## Review & Testing Checklist for Human
- [ ] **Accuracy of each convention**: These were inferred from commit
history by an AI, not dictated by a human. Read through each bullet and
verify it matches your actual intent — some rules may be stated too
broadly or too narrowly (e.g., is `console::Term` truly the only
acceptable output mechanism? Is `const fn` always preferred, or only in
specific contexts?).
- [ ] **Completeness**: Are there important conventions missing that
should be documented? For example, dependency management policies, PR
size expectations, or branch naming.
- [ ] **Commit message section**: The final section prescribes
conventional commits (`type(scope): description`). Confirm this is a
convention you want enforced — the commit history shows mixed adherence
(earlier commits don't follow it).
### Notes
- The original `pub(super)` rule is preserved and expanded with a
visibility-narrowing guideline derived from PRs #180 and #190.
- Guidelines about config handling (atomic writes, comment preservation,
locking) reflect the significant effort invested in PRs #131, #220,
#257, and #266.
- The lint-related sections mirror what's already enforced in
`Cargo.toml` `[lints.clippy]` and `[lints.rust]` — making them explicit
here helps reviewers who don't check the lint config.
Link to Devin session:
https://app.devin.ai/sessions/67611649d13b486dbe1ebf06c87acc47
Requested by: @sachiniyer
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/297"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Expanded and slimmed `REVIEW.md` into a guide for human-judged
conventions only; lint-enforced rules are omitted. Covers visibility
choices, error context with `anyhow::Result`, strict `--format json`,
optional `repo` arg with git-remote fallback, generated code via
`progenitor` and `cargo xtask` updates to `openapi.json`/help, config
writes via `update_config`, and conventional commits.
- **Migration**
- No migration needed; confirm the conventions match current
expectations.
<sup>Written for commit 74c602d.
Summary will update on new commits. <a
href="https://cubic.dev/pr/usedetail/cli/pull/297?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Sachin Iyer <siyer@detail.dev>
Sign up for freeto 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

@sachiniyer
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

refactor(config): rewrite update_config through the locked handle - #257

Merged
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config
Apr 21, 2026
Merged

refactor(config): rewrite update_config through the locked handle#257
sachiniyer merged 1 commit into
mainfrom
siyer/clarify-update-config

Conversation

@sachiniyer

@sachiniyersachiniyer commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Why this is a refactor, not a bug fix

The linked bug claimed the shadowed handle drops early, releasing the flock mid-write. It does not — Rust shadowing keeps the old binding alive until end of scope. I verified with a subprocess flock -n test that the lock is still held after the shadow. The bug report's failing test most likely picked up the tiny artifact between function return (Drop releases the flock) and the main thread flipping its "in-update" flag to false, not a real mid-write unlock.

No behavior change; existing concurrent_update_config_does_not_corrupt still passes.

Test plan

  • cargo test --lib config::storage — 15 pass
  • cargo clippy -- -D warnings clean
  • cargo fmt --check clean

🤖 Generated with Claude Code


Open in Devin Review

`update_config` opened a second `File::create` handle solely to truncate
and write, which made `file.unlock()` act on a handle that was never
locked. The real lock release happened implicitly via `Drop` at end of
scope — functionally correct but confusing, and it made the code look
like it contained the race described in bug_e79362bd (github #229).
Rewind, truncate in place (`set_len(0)`), and write through the handle
that already holds the flock. `file.unlock()` now releases the lock
that was actually acquired. No behavior change; existing
`concurrent_update_config_does_not_corrupt` still passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

No issues found across 1 file

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment threadsrc/config/storage.rs

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.

🚩 save_config has the same pattern the PR fixes in update_config

The save_config function at src/config/storage.rs:65-73 uses File::create followed by lock_exclusive, which means data is written to the file after locking, but there's a window between File::create (which truncates the file) and lock_exclusive where another reader could see an empty/partial file. This is a pre-existing issue with a similar pattern to what the PR fixes in update_config. It's less critical here because save_config doesn't read-then-write (no TOCTOU), but a concurrent load_config (which doesn't acquire a lock) could read a truncated file during the window between File::create and write_all.

(Refers to lines 68-71)

Open in Devin Review

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

Comment threadsrc/config/storage.rs
Comment on lines +102 to +103
(&file).seek(SeekFrom::Start(0))?;
file.set_len(0)?;

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.

📝 Info: Old code silently released the exclusive lock before writing

The old code let file = File::create(&path)?; at line 98 shadowed the file variable, causing the original locked file descriptor to be dropped. This released the exclusive lock before the write, creating a TOCTOU race window where another process could interleave. The new code correctly reuses the same handle via seek + set_len, maintaining the lock throughout the entire read-modify-write cycle. This is the core correctness improvement of the PR.

Open in Devin Review

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

@sachiniyer
sachiniyer merged commit 7f61a89 into mainApr 21, 2026
13 checks passed
@sachiniyer
sachiniyer deleted the siyer/clarify-update-config branch April 21, 2026 23:10
sachiniyer added a commit that referenced this pull request Apr 23, 2026
## Summary
Patch release rolling up the seven fixes merged since 0.2.1:
- fix(bugs): print empty-results hint when `--vulns` alone yields no
matches (#264)
- fix(datetime): floor sub-second negative timestamps instead of
snapping to epoch (#262)
- chore(deps): bump rustls-webpki to 0.103.13 for RUSTSEC-2026-0104
(#263)
- fix(repos): normalize whitespace in repo identifiers before lookup
(#261)
- fix(git): parse GitHub remotes with embedded http(s) credentials
(#260)
- fix(auth): redirect browser and surface OAuth errors on PKCE callback
failure (#258)
- refactor(config): rewrite `update_config` through the locked handle
(#257)
On merge, the release workflow will tag `v0.2.2` and publish platform
artifacts via cargo-dist.
## Test plan
- [x] `cargo build` succeeds with version 0.2.2
- [ ] Tag `v0.2.2` is created on merge and release workflow publishes
artifacts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/265"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sachiniyer added a commit that referenced this pull request May 16, 2026
## Summary
Expands `REVIEW.md` from a single rule (no `pub(super)`) into a
comprehensive set of review guidelines extracted from the full commit
history (259 commits). Each section documents a convention that has been
repeatedly enforced through past PRs and/or the lint configuration in
`Cargo.toml` / `lib.rs`.
New sections cover: visibility, error handling, type safety & casts,
imports & paths, generated code & OpenAPI, output format discipline,
config file handling, concurrency, testing, CLI argument design, code
organization, and commit messages.
Documentation-only change — no code modified.
## Review & Testing Checklist for Human
- [ ] **Accuracy of each convention**: These were inferred from commit
history by an AI, not dictated by a human. Read through each bullet and
verify it matches your actual intent — some rules may be stated too
broadly or too narrowly (e.g., is `console::Term` truly the only
acceptable output mechanism? Is `const fn` always preferred, or only in
specific contexts?).
- [ ] **Completeness**: Are there important conventions missing that
should be documented? For example, dependency management policies, PR
size expectations, or branch naming.
- [ ] **Commit message section**: The final section prescribes
conventional commits (`type(scope): description`). Confirm this is a
convention you want enforced — the commit history shows mixed adherence
(earlier commits don't follow it).
### Notes
- The original `pub(super)` rule is preserved and expanded with a
visibility-narrowing guideline derived from PRs #180 and #190.
- Guidelines about config handling (atomic writes, comment preservation,
locking) reflect the significant effort invested in PRs #131, #220,
#257, and #266.
- The lint-related sections mirror what's already enforced in
`Cargo.toml` `[lints.clippy]` and `[lints.rust]` — making them explicit
here helps reviewers who don't check the lint config.
Link to Devin session:
https://app.devin.ai/sessions/67611649d13b486dbe1ebf06c87acc47
Requested by: @sachiniyer
<!-- devin-review-badge-begin -->
---
<a href="https://app.devin.ai/review/usedetail/cli/pull/297"
target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Expanded and slimmed `REVIEW.md` into a guide for human-judged
conventions only; lint-enforced rules are omitted. Covers visibility
choices, error context with `anyhow::Result`, strict `--format json`,
optional `repo` arg with git-remote fallback, generated code via
`progenitor` and `cargo xtask` updates to `openapi.json`/help, config
writes via `update_config`, and conventional commits.
- **Migration**
- No migration needed; confirm the conventions match current
expectations.
<sup>Written for commit 74c602d.
Summary will update on new commits. <a
href="https://cubic.dev/pr/usedetail/cli/pull/297?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Sachin Iyer <siyer@detail.dev>
Sign up for freeto 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

@sachiniyer