Skip to content

Conversation

@KoltesDigital
Copy link

@KoltesDigital KoltesDigital commented Nov 17, 2025

Description

This patch adds a file lock to ensure only one process uses Scoop. Extra processes wait 1 second and retry, so dequeuing is not handled in order.

The diff shows a lot of modified lines, but it's actually just an indent increase due to the try-finally block.

Motivation and Context

I've created a scheduled task to automate scoop update * every morning or at every startup. Whether it's good or bad is another topic :) But I've noticed some conflicts. For instance, scoop list shows install failed while the app is being installed. I suspect that I got corrupted git-buckets due to parallel updates too.

This is also in line with my expectations of a service. IMHO Scoop acts like a session-wide service that several processes can access, so it must ensure it's safe to do so. The fact that Scoop exclusively works with PS1 scripts is an implementation detail.

Note that global installs made by different process owners could still conflicts. A better version would be to pinpoint which commands are subjects to lock, and in these cases whether the lock should be global. This would also have the benefit not to lock for things like displaying help. I'd love to have that, but there's more effort to put, so please tell me what you think first.

How Has This Been Tested?

Locally with multiple shells.

Checklist:

  • I have read the Contributing Guide.
  • I have ensured that I am targeting the develop branch.
  • I have updated the documentation accordingly.
  • I have updated the tests accordingly.
  • I have added an entry in the CHANGELOG.

Summary by CodeRabbit

  • New Features

    • Adds a file locking mechanism to prevent concurrent executions; callers see a one-time waiting message if a run is already active.
  • Bug Fixes

    • Ensures the lock is always released after execution.
    • Unknown or unsupported commands now emit a clear warning and exit with a non-zero status.
  • Chores

    • Updated ignore rules to exclude lock files.

@coderabbitai
Copy link

coderabbitai bot commented Nov 17, 2025

Walkthrough

Implements a file-based exclusive lock in bin/scoop.ps1, adds a .lock ignore to .gitignore, and documents the change in CHANGELOG.md. The script prints a single waiting message while retrying exclusive open, wraps subcommand dispatch in try/finally to always release the lock, and warns on unknown commands.

Changes

Cohort / File(s) Summary
Configuration
\.gitignore
Adds rule to ignore a .lock file.
Changelog
CHANGELOG.md
Adds Unreleased entry: core: Add file lock to prevent concurrent executions.
CLI Locking & Control Flow
bin/scoop.ps1
Implements exclusive file lock with a non-blocking retry loop that prints a single waiting message; wraps subcommand dispatch in try/finally to ensure lock disposal; restores alias re-aliasing inside the locked region; adds unknown-command warning and exit code 1; restructures command dispatch flow.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Script as scoop.ps1
    participant LockFile as Lock File
    participant Command as Subcommand

    User->>Script: invoke scoop command
    Script->>LockFile: attempt exclusive open (write)
    alt Lock unavailable
        Note over Script,LockFile `#f6f0d9`: print waiting message once\nretry until writable
        LockFile-->>Script: lock acquired
    else Lock acquired immediately
        LockFile-->>Script: lock acquired
    end
    Script->>Script: restore aliases / re-aliasing
    Script->>Command: dispatch selected subcommand
    Command-->>Script: returns result or error
    Script->>LockFile: close/dispose lock (finally)
    Script-->>User: exit (success or code 1 for unknown command)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review focus:
    • bin/scoop.ps1 lock acquisition loop (single waiting message, retry/backoff behavior).
    • Guaranteed disposal of the lock stream across success, failure, and early exits.
    • Correct placement and effect of alias re-aliasing inside the locked region.
    • Unknown-command handling and appropriate exit code propagation.

Poem

🐰 I found a tiny padlock bright,
I waited once beneath the light,
No clashing paws, the path is clear,
One hop, one lock — no need to fear. 🔒

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add file lock to prevent concurrent executions' directly and clearly summarizes the main change: implementing a file-based lock mechanism in the scoop.ps1 script to prevent multiple concurrent Scoop processes from running simultaneously.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CHANGELOG.md (1)

5-11: Fill in the CHANGELOG reference instead of using an empty [#] link

The new feature entry uses a placeholder link:

- **core:** Add file lock to prevent concurrent executions ([#](https://github.com/ScoopInstaller/Scoop/issues/))

This will render as a broken/meaningless reference. Please either point it at the actual issue/PR number or drop the link entirely, e.g.:

- - **core:** Add file lock to prevent concurrent executions ([#](https://github.com/ScoopInstaller/Scoop/issues/))
+ - **core:** Add file lock to prevent concurrent executions
+   # or, once known:
+ - **core:** Add file lock to prevent concurrent executions ([#6557](https://github.com/ScoopInstaller/Scoop/pull/6557))
🧹 Nitpick comments (1)
.gitignore (1)

1-10: Consider anchoring the ignore rule to the repo root

The lock file used by bin/scoop.ps1 lives at the repo root ($PSScriptRoot\..\.lock), but .gitignore currently ignores any .lock file anywhere:

.lock

If you only intend to ignore the root lock file, anchoring makes this more precise:

- .lock
+ /.lock

Not a blocker, but it reduces the chance of accidentally hiding unrelated .lock files elsewhere in the tree.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03d831d and 6dfb70a.

📒 Files selected for processing (3)
  • .gitignore (1 hunks)
  • CHANGELOG.md (1 hunks)
  • bin/scoop.ps1 (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-31T01:35:53.228Z
Learnt from: z-Fng
Repo: ScoopInstaller/Scoop PR: 6473
File: libexec/scoop-download.ps1:105-109
Timestamp: 2025-08-31T01:35:53.228Z
Learning: In the Scoop codebase, the `abort` function calls `exit` to terminate the entire PowerShell script execution rather than throwing catchable exceptions. Functions that call `abort` on errors cannot be wrapped in try-catch blocks for error handling.

Applied to files:

  • bin/scoop.ps1
🔇 Additional comments (1)
bin/scoop.ps1 (1)

24-73: Command dispatch under the lock looks solid and keeps the lock lifecycle well-scoped

The try { ... } finally { $lock_stream.Close(); $lock_stream.Dispose() } structure around the subcommand dispatch is a good fit here:

  • $subCommand routing for help/version/known commands remains clear and preserves existing behavior.
  • Unknown commands now warn and exit with code 1, which is appropriate for CLI ergonomics.
  • Closing/disposing the lock stream in finally correctly ensures the lock is released on both success and failure paths (exceptions, early returns, etc.), so the mutex doesn’t leak.

Apart from the lock acquisition concerns noted above, this part of the change reads cleanly and keeps the new global serialization behavior nicely contained.

Comment on lines 9 to 29
$lock_file_path = "$PSScriptRoot\..\.lock"
$lock_show_waiting_message = $true

# for aliases where there's a local function, re-alias so the function takes precedence
$aliases = Get-Alias | Where-Object { $_.Options -notmatch 'ReadOnly|AllScope' } | ForEach-Object { $_.Name }
Get-ChildItem Function: | Where-Object -Property Name -In -Value $aliases | ForEach-Object {
Set-Alias -Name $_.Name -Value Local:$($_.Name) -Scope Script
while (-not $lock_stream.CanWrite) {
try {
$lock_stream = [System.IO.File]::Open($lock_file_path, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
} catch {
if ($lock_show_waiting_message) {
'Waiting for exclusive access...'
$lock_show_waiting_message = $false
}
Start-Sleep -Seconds 1
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Tighten lock acquisition error handling to avoid masking non-contention failures

The lock loop works correctly for normal contention, but the generic catch means any failure opening the lock file (e.g. permission error, invalid path) will cause an infinite retry with only a single "Waiting for exclusive access..." line:

$lock_file_path = "$PSScriptRoot\..\.lock"
$lock_show_waiting_message = $true

while (-not $lock_stream.CanWrite) {
    try {
        $lock_stream = [System.IO.File]::Open($lock_file_path, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
    } catch {
        if ($lock_show_waiting_message) {
            'Waiting for exclusive access...'
            $lock_show_waiting_message = $false
        }
        Start-Sleep -Seconds 1
    }
}

That makes genuine configuration/IO problems look like normal queueing and leaves the user with a hung scoop process and no actionable error.

It would be safer to:

  • Only retry for contention-style errors (e.g. System.IO.IOException from a sharing violation), and
  • Let other exceptions bubble to the caller so the failure is visible.
  • Emit the waiting message via warn (or similar) so it doesn’t pollute the success pipeline.

For example:

-$lock_file_path = "$PSScriptRoot\..\.lock"
-$lock_show_waiting_message = $true
-
-while (-not $lock_stream.CanWrite) {
-    try {
-        $lock_stream = [System.IO.File]::Open($lock_file_path, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
-    } catch {
-        if ($lock_show_waiting_message) {
-            'Waiting for exclusive access...'
-            $lock_show_waiting_message = $false
-        }
-        Start-Sleep -Seconds 1
-    }
-}
+$lock_file_path = "$PSScriptRoot\..\.lock"
+$lock_show_waiting_message = $true
+$lock_stream = $null
+
+while ($true) {
+    try {
+        $lock_stream = [System.IO.File]::Open(
+            $lock_file_path,
+            [System.IO.FileMode]::Create,
+            [System.IO.FileAccess]::Write,
+            [System.IO.FileShare]::None
+        )
+        break
+    } catch [System.IO.IOException] {
+        if ($lock_show_waiting_message) {
+            warn 'Waiting for exclusive access...'
+            $lock_show_waiting_message = $false
+        }
+        Start-Sleep -Seconds 1
+    }
+}

This preserves the 1‑second retry behavior for true contention, but fails fast (with a clear error) on misconfiguration or other unexpected IO problems, and keeps the waiting message off the success pipeline.

🤖 Prompt for AI Agents
In bin/scoop.ps1 around lines 9 to 22, the catch block currently swallows all
exceptions and prints a single plain string, causing infinite retries on
non-contention errors and polluting success output; change the logic to only
retry for contention-style IO errors (i.e. when the exception is a
System.IO.IOException indicating a sharing violation), rethrow any other
exceptions so they surface to the caller, and emit the waiting message with
Write-Warning (or similar) instead of writing to stdout; keep the 1-second retry
loop for true contention but fail fast for other IO/configuration errors.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
bin/scoop.ps1 (1)

9-21: Still retries forever on any IOException and prints status to stdout

Catching all System.IO.IOException values here still turns any IO failure (not just sharing violations) into an infinite 1‑second retry loop with a single "Waiting for exclusive access..." line written to the success pipeline. That leaves users with a hung scoop when, e.g., the lock path is inaccessible for non‑contention reasons.

It would be safer to:

  • Only retry on sharing‑violation style IO errors.
  • Re‑throw other IO exceptions so misconfiguration is visible.
  • Emit the waiting message via warn (or similar) instead of writing a bare string.

For example:

-$lock_file_path = "$PSScriptRoot\..\.lock"
-$lock_show_waiting_message = $true
-
-while (-not $lock_stream.CanWrite) {
-    try {
-        $lock_stream = [System.IO.File]::Open($lock_file_path, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
-    } catch [System.IO.IOException] {
-        if ($lock_show_waiting_message) {
-            'Waiting for exclusive access...'
-            $lock_show_waiting_message = $false
-        }
-        Start-Sleep -Seconds 1
-    }
-}
+$lock_file_path = "$PSScriptRoot\..\.lock"
+$lock_show_waiting_message = $true
+$lock_stream = $null
+
+while ($true) {
+    try {
+        $lock_stream = [System.IO.File]::Open(
+            $lock_file_path,
+            [System.IO.FileMode]::Create,
+            [System.IO.FileAccess]::Write,
+            [System.IO.FileShare]::None
+        )
+        break
+    } catch [System.IO.IOException] {
+        # Only treat sharing violations as contention; surface other IO problems.
+        $errorCode = $_.Exception.HResult -band 0xFFFF
+        if ($errorCode -notin 32, 33) {
+            throw
+        }
+
+        if ($lock_show_waiting_message) {
+            warn 'Waiting for exclusive access...'
+            $lock_show_waiting_message = $false
+        }
+        Start-Sleep -Seconds 1
+    }
+}

This keeps the queueing behavior for real contention while failing fast and noisily for unexpected IO errors, and keeps lock‑wait noise off the success stream. Based on learnings.

#!/bin/bash
# Optional: confirm how Scoop currently reports waiting vs. failure.
# Run this inside a Scoop dev checkout after applying (or not applying) the patch:
# 1) Start one long-running `scoop` process that holds the lock.
# 2) Start a second `scoop` and observe:
#    - whether waiting messages go to stdout vs. stderr
#    - behavior if you temporarily make the repo root read-only to force a non-sharing IOException.
echo "Manually verify lock behavior under contention and misconfigured lock path."
🧹 Nitpick comments (1)
.gitignore (1)

4-4: Scope .lock ignore to repo root (optional)

Given the lock path is $PSScriptRoot\..\.lock, you can narrow the ignore to the root file only to avoid hiding any other .lock files:

-.lock
+/.lock

Not required, but slightly clearer about intent.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6dfb70a and 2de0ec1.

📒 Files selected for processing (3)
  • .gitignore (1 hunks)
  • CHANGELOG.md (1 hunks)
  • bin/scoop.ps1 (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-31T01:39:35.684Z
Learnt from: z-Fng
Repo: ScoopInstaller/Scoop PR: 6473
File: libexec/scoop-download.ps1:135-135
Timestamp: 2025-08-31T01:39:35.684Z
Learning: In the Scoop codebase, `Invoke-ScoopDownload` in lib/download.ps1 calls `abort` when hash verification fails, which terminates script execution completely rather than trying remaining mirrors. The scoop-download.ps1 script should maintain consistency with this behavior by using `continue app_loop` to skip to the next app rather than trying remaining mirrors for the current app.

Applied to files:

  • bin/scoop.ps1
📚 Learning: 2025-08-31T01:35:53.228Z
Learnt from: z-Fng
Repo: ScoopInstaller/Scoop PR: 6473
File: libexec/scoop-download.ps1:105-109
Timestamp: 2025-08-31T01:35:53.228Z
Learning: In the Scoop codebase, the `abort` function calls `exit` to terminate the entire PowerShell script execution rather than throwing catchable exceptions. Functions that call `abort` on errors cannot be wrapped in try-catch blocks for error handling.

Applied to files:

  • bin/scoop.ps1
🔇 Additional comments (2)
bin/scoop.ps1 (1)

24-73: Guarded dispatch + unknown‑command handling look good

Wrapping subcommand dispatch in try/finally to ensure the lock stream is closed, restoring the alias re-aliasing inside the guarded region, and adding an explicit default that warns and exits with code 1 for unknown commands all look consistent and correct for the main entrypoint.

CHANGELOG.md (1)

5-10: Changelog entry is consistent with existing style

The new **core:** Add file lock to prevent concurrent executions entry matches the existing formatting and correctly describes the change.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
bin/scoop.ps1 (1)

9-29: Lock acquisition loop is solid; consider clearer termination and non-polluting status output

The lock logic looks correct and much safer now: catching only [System.IO.IOException] and filtering by HResult 32/33 prevents infinite retries on non-contention errors. A couple of small refinements could make this more robust and readable:

  • The condition while (-not $lock_stream.CanWrite) currently relies on $lock_stream being $null and Set-StrictMode -Off, so $null.CanWrite evaluates to $null, making -not $null $true. This is subtle and easy to break with future refactors. Consider a more explicit pattern such as:

    $lock_stream = $null
    while (-not $lock_stream) {
        try {
            $lock_stream = [System.IO.File]::Open(
                $lock_file_path,
                [System.IO.FileMode]::Create,
                [System.IO.FileAccess]::Write
            )
        } catch [System.IO.IOException] {
            # existing HResult handling...
        }
    }

    (Optionally reassign $lock_stream = $null in the catch block if you choose a different loop condition.)

  • The bare string 'Waiting for exclusive access...' writes to the success pipeline. For callers that parse scoop output (e.g., scoop list | ...), this introduces unexpected noise whenever contention happens. Emitting this via warn (from core.ps1) or Write-Warning instead keeps user feedback on stderr and preserves the success stream for structured output:

    if ($lock_show_waiting_message) {
        warn 'Waiting for exclusive access...'
        $lock_show_waiting_message = $false
    }
  • Optional: if you want the exclusivity to be more obvious to future readers, you could switch to the 4‑argument overload and pass [System.IO.FileShare]::None explicitly, even though the current 3‑argument overload already defaults to FileShare.None.

These are all incremental improvements; the current behavior for contention vs. non‑contention errors is already correct.

🧹 Nitpick comments (1)
bin/scoop.ps1 (1)

31-80: try/finally wrapping of subcommand dispatch looks correct; minor defensive cleanup possible

Wrapping the entire subcommand dispatch in a try { ... } finally { ... } is a good way to guarantee the lock is released on normal completion and on terminating errors thrown within the try block. A couple of small notes:

  • Given the acquisition loop only falls through once $lock_stream is a writable stream, $lock_stream should always be non‑null when entering finally. To make this more future‑proof against changes to the loop, you could defensively guard the cleanup:

    finally {
        if ($lock_stream) {
            $lock_stream.Close()
            $lock_stream.Dispose()
        }
    }
  • Calling both Close() and Dispose() on a FileStream is harmless but redundant; either is sufficient. Not urgent, but you could simplify to just Dispose().

  • The new default branch for unknown $subCommand values:

    warn "scoop: '$subCommand' isn't a scoop command. See 'scoop help'."
    exit 1

    is a nice improvement for scriptability: it surfaces a clear error message and a non‑zero exit code while still running under the lock, so the lock is released via finally on the way out.

Overall, the restructuring under try/finally looks sound; the above points are minor polish.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2de0ec1 and e8616e3.

📒 Files selected for processing (3)
  • .gitignore (1 hunks)
  • CHANGELOG.md (1 hunks)
  • bin/scoop.ps1 (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • .gitignore
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-31T01:39:35.684Z
Learnt from: z-Fng
Repo: ScoopInstaller/Scoop PR: 6473
File: libexec/scoop-download.ps1:135-135
Timestamp: 2025-08-31T01:39:35.684Z
Learning: In the Scoop codebase, `Invoke-ScoopDownload` in lib/download.ps1 calls `abort` when hash verification fails, which terminates script execution completely rather than trying remaining mirrors. The scoop-download.ps1 script should maintain consistency with this behavior by using `continue app_loop` to skip to the next app rather than trying remaining mirrors for the current app.

Applied to files:

  • bin/scoop.ps1
📚 Learning: 2025-08-31T01:35:53.228Z
Learnt from: z-Fng
Repo: ScoopInstaller/Scoop PR: 6473
File: libexec/scoop-download.ps1:105-109
Timestamp: 2025-08-31T01:35:53.228Z
Learning: In the Scoop codebase, the `abort` function calls `exit` to terminate the entire PowerShell script execution rather than throwing catchable exceptions. Functions that call `abort` on errors cannot be wrapped in try-catch blocks for error handling.

Applied to files:

  • bin/scoop.ps1

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
bin/scoop.ps1 (1)

23-25: Consider using warn for consistency with Scoop's error handling patterns.

The Write-Host function works, but using the warn function (imported from core.ps1) would be more consistent with how Scoop handles user-facing messages elsewhere in the codebase.

Apply this diff:

-        if ($lock_show_waiting_message) {
-            Write-Host 'Waiting for exclusive access...'
-            $lock_show_waiting_message = $false
-        }
+        if ($lock_show_waiting_message) {
+            warn 'Waiting for exclusive access...'
+            $lock_show_waiting_message = $false
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8616e3 and 520188c.

📒 Files selected for processing (3)
  • .gitignore (1 hunks)
  • CHANGELOG.md (1 hunks)
  • bin/scoop.ps1 (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-31T01:39:35.684Z
Learnt from: z-Fng
Repo: ScoopInstaller/Scoop PR: 6473
File: libexec/scoop-download.ps1:135-135
Timestamp: 2025-08-31T01:39:35.684Z
Learning: In the Scoop codebase, `Invoke-ScoopDownload` in lib/download.ps1 calls `abort` when hash verification fails, which terminates script execution completely rather than trying remaining mirrors. The scoop-download.ps1 script should maintain consistency with this behavior by using `continue app_loop` to skip to the next app rather than trying remaining mirrors for the current app.

Applied to files:

  • bin/scoop.ps1
📚 Learning: 2025-08-31T01:35:53.228Z
Learnt from: z-Fng
Repo: ScoopInstaller/Scoop PR: 6473
File: libexec/scoop-download.ps1:105-109
Timestamp: 2025-08-31T01:35:53.228Z
Learning: In the Scoop codebase, the `abort` function calls `exit` to terminate the entire PowerShell script execution rather than throwing catchable exceptions. Functions that call `abort` on errors cannot be wrapped in try-catch blocks for error handling.

Applied to files:

  • bin/scoop.ps1
🔇 Additional comments (1)
.gitignore (1)

4-4: LGTM!

Correctly ignores the lock file created by the locking mechanism.

Comment on lines +77 to 79
} finally {
$lock_stream.Dispose()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add null check before disposing the lock stream.

If an unexpected exception occurs during lock acquisition (before $lock_stream is successfully assigned), the finally block will attempt to call Dispose() on a null object, which will throw a NullReferenceException and mask the original exception.

Apply this diff to add a defensive null check:

 } finally {
-    $lock_stream.Dispose()
+    if ($null -ne $lock_stream) {
+        $lock_stream.Dispose()
+    }
 }
🤖 Prompt for AI Agents
In bin/scoop.ps1 around lines 77 to 79, the finally block disposes $lock_stream
without checking for null which can throw if assignment failed; update the
finally block to check if $lock_stream is not $null before calling Dispose()
(i.e., wrap Dispose() call in an if ($lock_stream -ne $null) {
$lock_stream.Dispose() } or use conditional invocation) to avoid masking the
original exception.

@rashil2000
Copy link
Member

Question: is there a shared persistent state that Scoop reads/writes when updating or installing apps? As far as I know Scoop was one of the few apps that supported doing multiple installs/updates (of different apps of course) at the same time.

Running a scheduled task for update is fine, but what's the use case of running list or status while the apps are getting updated?

@KoltesDigital
Copy link
Author

@rashil2000 more on the context, to be honest... and also to get feedback from you :)

Thought I'm a developer, I'm dealing with IT for a small company, so I'm trying to use my dev habits to manage PCs. Workstations run Windows, and we have a couple of in-house apps. I've chosen to install them and keep them updated through Scoop. Installation itself is automatized through Intune depending on groups the user or machine belong to, but it can't handle updates. So the first thing Intune does is install Scoop, and then Intune only install apps through Scoop. This allows user to install extra apps. Intune also installs a scheduled task as described, to get things up-to-date. We're not big enough to get fixed, verified versions of apps... so I prefer to always have the latest versions, with the tiny risk that an app may break.

Last detail: Intune can install its things as device or as user, and it's kind of a nightmare regarding permissions. When as device, install process belongs to SYSTEM account, and is elevated. When as user, install process belongs to the user, and is automatically elevated iff the user is admin. This last point is really painful: user context is not the same for all users. For instance, when Intune installs Scoop for admin users, buckets (Git repos) somehow get wrong permissions, and non-elevated shells cannot use Scoop anymore... so there are some tricks to get back on track.

Because Scoop takes care of updates, I don't have to deal with that on my apps, and that's great. Whenever CICD pushes a new version, scoop update ... does the job. However, my users won't run Scoop. That's why the scheduled task runs it every morning. However, we can have several updates per day, most of them are bugfixes to the previous one, so we can't wait the next morning. So in the main app, there's a regular check to its manifest (directly through https) to see whether there's a new version, and if so, offers to the user to run scoop update <app> under the hood. Well I know, it's not far away from reimplementing a whole update process... but at least Scoop does a part of that.

Many users reported many times that the app couldn't launch anymore. My assumption is that they launched the app right at startup, the app tells them to update it, so they do it, but the scheduled task is also doing it, and boom. Thankfully, the fix is simply uninstall and reinstall.

list or status were just examples, they show an incorrect state but it's harmless. Running concurrent updates is not. If you're willing to consider a dedicated lock for each app, that would be awesome. Please tell me and I can try to do it. BTW I've put the lock at the repo's root, but if we're getting a bunch of them, another location surely is better, please tell me an advice.

@rashil2000
Copy link
Member

rashil2000 commented Nov 23, 2025

This should ideally be done on a per-app basis. The SCOOP/apps/<app-name>/ is one location to place the lock in.

We can also show the status 'Updating' in the list or status command if the lock file exists for that app.

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