Skip to content

fix(variables): correct scripted-variable persistence races and cross-request propagation - #8406

Merged
vijayh-bruno merged 6 commits into
usebruno:mainfrom
sanish-bruno:feat/variable-persistence-file-lock
Jul 3, 2026
Merged

vijayh-bruno merged 6 commits into
usebruno:mainfrom
sanish-bruno:feat/variable-persistence-file-lock

Conversation

@sanish-bruno

@sanish-bruno sanish-bruno commented Jun 29, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Three fixes to scripted variable persistence (bru.setEnvVar / setCollectionVar / setGlobalEnvVar):

1. Per-path file lock for env writes (withFileLock in filesystem.js)

Wraps the read-then-write critical sections in renderer:save-environment and the global-env save path. Without serialization, two rapid scripted persists on the same env file interleave and drop one update. Per-path async mutex; different files still write in parallel.

2. Collection-var propagation across requests in one run (applyCollectionVarsToCollectionRoot in network/index.js)

mergeVars rebuilds request.collectionVariables from collection.root every iteration, so a script's setCollectionVar was invisible to the next request unless we wrote it back to root. Helper rebuilds the enabled slice from script output (missing keys → deleted), preserves disabled vars, writes to collection.root and collection.draft?.root.

3. Removes the _scriptRequestUid stale-update gate

#8315 added a gate that dropped script-emitted updates from "superseded" requests. Removing it because:

  • Script errors already let partial mutations land — gating cancellation but not errors is incoherent.
  • The sandbox runs to completion regardless of Cancel; the gate suppressed output, not work.
  • The single slot got clobbered by parallel folder-runner iterations and nested bru.runRequest, dropping legitimate in-flight updates.

Deletes the field, the 6 call sites, the delete in _clearScriptCollectionBaselines, and the requestUid thread-through in persistActiveEnvironment / collectionVariablesUpdateEvent / globalEnvironmentsUpdateEvent.

Behavior change: if a user cancels a request after its script has called setEnvVar(..., {persist:true}) but before the update reaches the renderer, the update now lands — same as if the script had errored, or the request had taken 50ms longer. Worth a changelog note.

Scope / non-goals

Parallel folder-runner iterations (runInParallel: true) share one env state and one collection.root across iterations. The file lock fixes the disk-write race on the shared env file, but cross-iteration variable visibility (iteration A's setEnvVar becoming visible inside iteration B's script) is accepted behavior and intentionally not addressed here. Isolating per-iteration state would be a separate architectural change.

Tests

  • filesystem.spec.js — withFileLock semantics (serialization, per-path independence, error isolation, queue cleanup, read-modify-write).
  • tests/scripting/bru-api/collection-var-multi-request — Playwright e2e: req-1 writes counter='1', req-2 must read it.
  • Removed a stale runtime.spec.js test asserting absence of persistentEnvVariables (meaningless since persistence is default).

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Summary by CodeRabbit

  • New Features
    • Script-driven collection variable updates now write back into the active collection during a run, so later requests can observe earlier changes.
    • Added per-environment-file locking to coordinate concurrent environment saves and color updates.
  • Bug Fixes
    • Improved script-driven environment and variable updates so they’re no longer dropped when requests are superseded.
    • Runner skip handling is safer by only updating valid runner entries.
  • Tests
    • Added Jest coverage for per-path file locking and collection-variable write-back behavior.
    • Added Playwright coverage for multi-request collection variable propagation.

@sanish-bruno sanish-bruno changed the title Feat/variable persistence file lock fix(variables): correct scripted-variable persistence races and cross-request propagation Jun 29, 2026
@coderabbitai

coderabbitai Bot commented Jun 29, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 672cd5f4-59c7-4ed9-8e8c-a742dc467d13

📥 Commits

Reviewing files that changed from the base of the PR and between 7a95d42 and 2fd9f2b.

📒 Files selected for processing (21)
  • packages/bruno-app/src/providers/App/useIpcEvents.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
  • packages/bruno-electron/src/ipc/collection.js
  • packages/bruno-electron/src/ipc/network/apply-collection-vars.js
  • packages/bruno-electron/src/ipc/network/apply-collection-vars.spec.js
  • packages/bruno-electron/src/ipc/network/index.js
  • packages/bruno-electron/src/store/workspace-environments.js
  • packages/bruno-electron/src/utils/filesystem.js
  • packages/bruno-electron/tests/utils/filesystem.spec.js
  • packages/bruno-js/tests/runtime.spec.js
  • tests/environments/api-deleteEnvVar/fixtures/collection/environments/Stage.bru
  • tests/scripting/bru-api/collection-var-multi-request/collection-var-multi-request.spec.ts
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/bruno.json
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/collection.bru
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/environments/Test.bru
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/req-1-write.bru
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/req-2-read-and-write.bru
  • tests/scripting/bru-api/collection-var-multi-request/init-user-data/collection-security.json
  • tests/scripting/bru-api/collection-var-multi-request/init-user-data/preferences.json
💤 Files with no reviewable changes (2)
  • tests/environments/api-deleteEnvVar/fixtures/collection/environments/Stage.bru
  • packages/bruno-js/tests/runtime.spec.js
✅ Files skipped from review due to trivial changes (3)
  • tests/scripting/bru-api/collection-var-multi-request/init-user-data/preferences.json
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/bruno.json
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/collection.bru
🚧 Files skipped from review as they are similar to previous changes (14)
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/environments/Test.bru
  • tests/scripting/bru-api/collection-var-multi-request/collection-var-multi-request.spec.ts
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/req-2-read-and-write.bru
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/req-1-write.bru
  • packages/bruno-electron/src/ipc/network/apply-collection-vars.js
  • tests/scripting/bru-api/collection-var-multi-request/init-user-data/collection-security.json
  • packages/bruno-electron/src/ipc/network/index.js
  • packages/bruno-app/src/providers/App/useIpcEvents.js
  • packages/bruno-electron/src/store/workspace-environments.js
  • packages/bruno-electron/src/ipc/network/apply-collection-vars.spec.js
  • packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
  • packages/bruno-electron/src/utils/filesystem.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js

Walkthrough

Adjusts scripted update gating, serializes environment file writes by path, and adds in-memory collection-variable write-back plus end-to-end coverage for multi-request propagation.

Changes

Collection Variable Propagation & File Lock

Layer / File(s) Summary
Remove requestUid gating
packages/bruno-app/src/providers/App/useIpcEvents.js, packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js, packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js, packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
Removes _scriptRequestUid tracking and request-based stale-update checks from scripted environment/runtime/global-environment update paths, and dispatches active-environment persistence without requestUid.
Serialize environment file writes
packages/bruno-electron/src/utils/filesystem.js, packages/bruno-electron/src/ipc/collection.js, packages/bruno-electron/src/store/workspace-environments.js, packages/bruno-electron/tests/utils/filesystem.spec.js
Adds withFileLock(pathname, fn) and uses it to wrap environment save flows so secrets persistence and file writes are serialized for the same path.
Apply script collection vars to root
packages/bruno-electron/src/ipc/network/apply-collection-vars.js, packages/bruno-electron/src/ipc/network/index.js, packages/bruno-electron/src/ipc/network/apply-collection-vars.spec.js, packages/bruno-js/tests/runtime.spec.js
Adds applyCollectionVarsToCollectionRoot, applies it after renderer notification, and updates unit coverage for collection-variable write-back behavior.
Cross-request propagation fixtures and specs
tests/scripting/bru-api/collection-var-multi-request/*
Adds a Playwright collection run and supporting Bruno fixtures that verify request 2 observes request 1’s collection-variable write in the same run.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • usebruno/bruno#6703: Shares the packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js scripted environment reducer path and related update handling.
  • usebruno/bruno#6970: Touches the same environment-color persistence path in packages/bruno-electron/src/ipc/collection.js.
  • usebruno/bruno#8315: Directly overlaps with the scripted environment and collection-variable persistence path updated here.

Suggested reviewers

  • helloanoop
  • lohit-bruno
  • naman-bruno
  • bijin-bruno
  • sid-bruno

Poem

A lock clicked softly, path by path,
While variables danced through runner math.
Request two found what request one had sown,
And the collection root kept pace, well-grown.
🧪✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: fixing scripted variable persistence races and cross-request propagation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.0)
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js

[]


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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js (1)

2480-2486: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Serialize collection-root variable saves as well.

With request UID gating removed, rapid script collection-var updates can issue multiple renderer:save-collection-root calls. That IPC handler still does an unlocked read/compare/write, so an older root save can finish last and drop newer script-written collection variables.

Suggested follow-up in `renderer:save-collection-root`
       const filePath = path.join(collectionPathname, filename);
-      const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
-      if (content === existing) return; // skip write if content unchanged
-      await writeFile(filePath, content);
+      await withFileLock(filePath, async () => {
+        const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
+        if (content === existing) return; // skip write if content unchanged
+        await writeFile(filePath, content);
+      });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js`
around lines 2480 - 2486, The collection-root save path in the collection
actions is still firing concurrent renderer:save-collection-root IPC calls,
which can let an older write overwrite newer script updates. Serialize these
saves in the save flow around findCollectionByUid,
transformCollectionRootToSave, and the ipcRenderer.invoke call, or add the same
single-flight/queueing protection inside renderer:save-collection-root so root
variable writes are processed in order and cannot race.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bruno-electron/src/ipc/collection.js`:
- Around line 773-782: The environment file save path in the collection IPC
handler already uses withFileLock, but renderer:update-environment-color still
updates the same file without that lock, which can cause races and lost changes.
Update the renderer:update-environment-color flow to use the same withFileLock
protection around its read-modify-write sequence on the environment file, and
keep the write path consistent with the existing collection save logic in
collection.js so all environment-file writers serialize through the same lock.

In `@packages/bruno-electron/src/store/workspace-environments.js`:
- Around line 189-196: The global environment save path still has an unprotected
write in updateGlobalEnvironmentColor, so it can race with the existing
withFileLock block around envFile.filePath. Update updateGlobalEnvironmentColor
to use the same file lock when persisting the color change, and ensure any write
to the environment file goes through the locked section so it cannot overwrite
concurrent variable saves.

In `@packages/bruno-electron/src/utils/filesystem.js`:
- Line 140: The filename check in isBrunoJsonFile is too loose because
endsWith('bruno.json') matches unrelated names like mybruno.json; update this
helper to verify the exact basename is bruno.json, using the existing
isBrunoJsonFile symbol in filesystem.js, so only the intended config file is
recognized.
- Around line 115-124: The lock key in withFileLock still uses path.resolve(),
which does not collapse symlink targets or case-only variants into the same
queue. Update the keying logic in withFileLock to use a filesystem-canonical
path (for example via a realpath-based helper) so all references to the same
file serialize through one lock and the _pathLocks map no longer splits
equivalent env file paths.

In `@packages/bruno-electron/tests/utils/filesystem.spec.js`:
- Around line 27-178: The file-lock tests are using POSIX-only hardcoded path
literals, which makes them brittle across platforms. Update the
`withFileLock(...)` test calls to use a shared path helper such as
`lockPath(...)` built with `path.join()` or `path.resolve()` instead of `/p/...`
strings. Keep the changes localized to the `filesystem.spec.js` cases that
exercise `withFileLock` so the locking behavior is still validated without
relying on platform-specific separators.

In
`@tests/scripting/bru-api/collection-var-multi-request/init-user-data/collection-security.json`:
- Line 4: The templated collection path is hardcoding a forward slash, which
makes the bootstrap path OS-specific and can break lookups on Windows. Update
the init-user-data entries in collection-security.json and preferences.json to
build the collection path using a fully joined, OS-agnostic path rather than
concatenating with “/”. Keep the same collectionPath placeholder, but ensure the
stored path is assembled through the existing path-joining mechanism used
elsewhere in the bootstrap data.

---

Outside diff comments:
In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js`:
- Around line 2480-2486: The collection-root save path in the collection actions
is still firing concurrent renderer:save-collection-root IPC calls, which can
let an older write overwrite newer script updates. Serialize these saves in the
save flow around findCollectionByUid, transformCollectionRootToSave, and the
ipcRenderer.invoke call, or add the same single-flight/queueing protection
inside renderer:save-collection-root so root variable writes are processed in
order and cannot race.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a24115cd-fe55-4980-8470-5ae187b7df21

📥 Commits

Reviewing files that changed from the base of the PR and between 1c9355e and d402f28.

📒 Files selected for processing (19)
  • packages/bruno-app/src/providers/App/useIpcEvents.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
  • packages/bruno-electron/src/ipc/collection.js
  • packages/bruno-electron/src/ipc/network/index.js
  • packages/bruno-electron/src/store/workspace-environments.js
  • packages/bruno-electron/src/utils/filesystem.js
  • packages/bruno-electron/tests/utils/filesystem.spec.js
  • packages/bruno-js/tests/runtime.spec.js
  • tests/environments/api-deleteEnvVar/fixtures/collection/environments/Stage.bru
  • tests/scripting/bru-api/collection-var-multi-request/collection-var-multi-request.spec.ts
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/bruno.json
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/collection.bru
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/environments/Test.bru
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/req-1-write.bru
  • tests/scripting/bru-api/collection-var-multi-request/fixtures/collections/collection-var-multi-request-test/req-2-read-and-write.bru
  • tests/scripting/bru-api/collection-var-multi-request/init-user-data/collection-security.json
  • tests/scripting/bru-api/collection-var-multi-request/init-user-data/preferences.json
💤 Files with no reviewable changes (2)
  • tests/environments/api-deleteEnvVar/fixtures/collection/environments/Stage.bru
  • packages/bruno-js/tests/runtime.spec.js

Comment thread packages/bruno-electron/src/ipc/collection.js
Comment thread packages/bruno-electron/src/store/workspace-environments.js
Comment thread packages/bruno-electron/src/utils/filesystem.js
Comment thread packages/bruno-electron/src/utils/filesystem.js Outdated
Comment thread packages/bruno-electron/tests/utils/filesystem.spec.js
…o manage stale updates

- Introduced a bounded sliding window mechanism for tracking recent script request UIDs, allowing for better handling of stale updates from superseded or cancelled requests.
- Updated relevant functions to utilize the new `isStaleScriptRequest` utility for improved clarity and maintainability.
- Enhanced the persistence logic to ensure that updates from retired requests do not interfere with newer requests, improving data integrity during concurrent operations.
- Added comprehensive tests to validate the behavior of the new UID management system and its impact on variable propagation across requests.
…untime tests

- Eliminated the test case that checked for the inclusion of persistentEnvVariables in the result, as it is no longer relevant.
- Cleaned up the test suite to enhance clarity and maintainability, focusing on relevant assertions for variable management.
…ove stale request handling

- Updated the `persistActiveEnvironment` and related functions to eliminate the requestUid parameter, streamlining the logic for environment persistence.
- Removed outdated checks for stale updates from superseded requests, enhancing clarity and maintainability.
- Deleted the `_scriptRequestUids` management code and associated tests, as they are no longer necessary for the current implementation.
- Improved the handling of environment updates to ensure accurate state management without stale data interference.
- Deleted the `hasJsExtension` utility function from the filesystem module as it was no longer needed.
- Cleaned up the exports to enhance clarity and maintainability of the codebase.
…pdates

- Updated the environment persistence logic in both `collection.js` and `workspace-environments.js` to utilize file locking during read and write operations.
- This change ensures that concurrent access to environment files is managed safely, preventing potential data corruption and enhancing reliability during updates.
@sanish-bruno
sanish-bruno force-pushed the feat/variable-persistence-file-lock branch from 7a95d42 to 2fd9f2b Compare June 30, 2026 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants