fix(code): serialize dcode self-upgrades across processes - #5252
Merged
Mason Daugherty (mdrxy) merged 3 commits intoAug 11, 2026
Conversation
Concurrently launched terminals each ran their own `uv tool install -U` against one tool environment, multiplying startup latency and turning a sibling's successful upgrade into a bogus "Auto-update failed" plus a 24h cooldown. Take a non-blocking cross-process file lock so exactly one process installs and the rest launch on the version they already have. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Mason Daugherty (mdrxy)
marked this pull request as ready for review
August 11, 2026 05:44
Extend the cross-process update lock to the two remaining paths that replace the installed package, and make its failure modes diagnosable. `dcode update` and `/update --deps` both run `uv tool install` against the same tool environment as an upgrade, so neither could be left unlocked without reopening the race the lock exists to close. Observability: both refusal branches logged at DEBUG, and the package logger sits at INFO unless DEEPAGENTS_CODE_DEBUG is set, so a skipped install left no evidence at all. A failed release was swallowed outright — and it is not harmless, since `UnixFileLock._release` clears its fd handle before unlocking, leaking an fd that still holds the lock and making every later attempt in the session report a phantom concurrent install. Also: - Replace the notification path's `ExitStack`, which was never closed on the refusal branch, with a plain `with`. Release had been delegated to refcount finalization of the suspended generator; a retained traceback would have pinned the process-global thread lock indefinitely. - Split the `mkdir`/`chmod` guard. A `chmod` refusal (CIFS/exFAT) is a hardening failure, not a locking failure, and abandoning the lock for it disabled the protection on every launch. - Document that `except Timeout` must precede `except OSError`: `Timeout` subclasses `TimeoutError`, hence `OSError`, so reordering turns every contended case into a fail-open. - Fail open when `filelock` cannot be imported, which a half-written `site-packages` mid-upgrade can cause. - Correct the `os.execv` rationale. Exec drops the lock on its own, since filelock's fd is non-inheritable under PEP 446. Releasing explicitly is still right — correctness should not rest on a dependency's fd behavior, and the restart can raise — but the stated mechanism was wrong, and the test docstring repeated it. - Stop blaming another session for in-process refusals in the TUI message. Tests: assert the lock is held *during* the install at all four call sites, not merely checked beforehand — shrinking any `with` to the boolean check alone previously left the whole suite green. Cover the acquire-failure fail-open, release failure, missing filelock, chmod refusal, real cross-thread exclusion, the production lock path, and its directory mode. The subprocess helper is now a context manager, so it no longer leaks a pipe.
Mason Daugherty (mdrxy)
deleted the
mdrxy/code/update-cross-process-lock
branch
August 11, 2026 06:29
Mason Daugherty (mdrxy)
pushed a commit
that referenced
this pull request
Aug 12, 2026
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Release notes preview: keep this section in sync with the package `CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`, not this PR description — keep them aligned anyway so the PR stays an accurate historical record for reviewers and anyone returning later._ --- ## [0.1.55](deepagents-code==0.1.54...deepagents-code==0.1.55) (2026-08-12) ### Features - Added a `/context` usage report for inspecting context consumption ([#5407](#5407)). - Added a cache and context status row for at-a-glance session state ([#5408](#5408)). - Added configurable warnings when a session exceeds the configured cost threshold ([#5405](#5405)). - Added support for persisting and reconfiguring ACP sessions ([#5366](#5366)). - Added automatic updates for installed plugins ([#5368](#5368)). - Added a toggle for diff line numbers ([#5427](#5427)). - `Ctrl+S` in `/auto model` now stores `[models].auto_classifier` ([#5313](#5313)). ### Fixes - Restored edit diffs in resumed threads ([#5391](#5391)). - Added a resume hint after crashes ([#5412](#5412)). - Clarified the project hooks trust prompt and stopped prompting for user hooks ([#5426](#5426)). - Cleared dynamic subagents on the next turn ([#5437](#5437)). - Improved grouped tool summaries by counting distinct targets ([#5409](#5409)). - Improved ask-user choice wrapping and selection styling ([#5442](#5442)). - Serialized `dcode` self-upgrades across processes ([#5252](#5252)). - Added warnings for stale dependencies in editable installs ([#5386](#5386)). - Hid incomplete extras from version output ([#5352](#5352)). - Removed the optional-provider startup tip ([#5421](#5421)). - Removed the “Message restored to input” toast ([#5253](#5253)). _End release notes preview._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: langchain-oss-automated-triage[bot] <248757908+langchain-oss-automated-triage[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #5250
When several dcode terminals are open at once, only one of them installs an update now — the others keep running the version they launched with and pick up the new one next launch.
Nothing coordinated the startup auto-update across processes. Every launching terminal independently decided an update was available and ran its own
uv tool install -Uagainst the same tool environment. The only lock in the area,_environment_mutation_lock, is an in-processasyncio.Lock.Two things partly hid this, neither of which is a lock. A terminal opened after another finished upgrading self-skips, because
is_installed_version_at_leastreads the on-disk distribution metadata — that covers "open a new tab later" but not "open four tabs at once". And uv takes its own lock on the tool directory, so concurrent installs queue rather than corrupt; but they wait, and a queued loser can exceed the upgrade timeout, be reported to the user asAuto-update failed, and write a 24-hour cooldown suppressing its next legitimate attempts. Upgrading in place is also actively hostile to running sessions:_prewarm_deferred_importsalready carries a defensive guard because a concurrent upgrade can make a not-yet-imported module transiently absent and crash the TUI.update_install_lockis a non-blocking context manager pairing a process-localthreading.Lockwith aFileLock(thread_local=False)on a dedicated lock file in the state directory — the same shape as_approval_state_lockand_trust_store_lock, so no new dependency. It yields whether the caller may install; losers return immediately rather than stalling startup behind an install they do not need, and record no failure cooldown, since nothing failed. It is taken by all three paths that replace the installed package: startup auto-update,/update, and the update notification's install action.Test plan
update_install_lockexclusion is tested against a real second OS process holding the same lock file, not a patched filelock.Made by Open SWE