Skip to content

Fix FilesystemTenancyBootstrapper::scopeCache() discarding configured cache paths - #1473

Draft
lukinovec wants to merge 6 commits into
masterfrom
scope-cache-fix
Draft

Fix FilesystemTenancyBootstrapper::scopeCache() discarding configured cache paths#1473
lukinovec wants to merge 6 commits into
masterfrom
scope-cache-fix

Conversation

@lukinovec

@lukinovec lukinovec commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

This problem is specific to file-driver stores, while tenancy.filesystem.scope_cache is set to true.

Ran into this while checking whether we could drop the separate 'parallel' cache store from our boilerplate's testing setup and instead just give the 'file' store a per-process path (framework/cache/data_<parallel testing token>), so each test process gets its own cache directory. Turns out scopeCache() discards configured paths entirely, so that has no effect (see below).

Using a different directory for the file cache store by setting cache.stores.file.path (either using config([...]), or directly in config/cache.php -- doesn't matter) has no effect -- FilesystemTenancyBootstrapper::scopeCache() ignores path/lock_path entirely and rewrites both to a hardcoded <storage>/framework/cache/data path on every tenancy()->initialize()/tenancy()->end():

// In `FilesystemTenancyBootstrapper::scopeCache()` (called both in `bootstrap()` and in `revert()`)
foreach ($stores as $name) {
    $path = $storagePath . '/framework/cache/data';
    $this->app['config']["cache.stores.{$name}.path"] = $path;
    $this->app['config']["cache.stores.{$name}.lock_path"] = $path;
    ...
}

Specific issues with hardcoding the path like this:

  • a store with a configured (non-default) path gets scoped to use the default one (what I described above)
  • lock_path is always overwritten with path, so a store with a separate lock directory loses that separation
  • revert() runs the same code, so it doesn't restore what the store was configured with before tenancy initialized -- it just re-applies the same hardcoded default. Central cache ends up using the wrong path after ending tenancy.

The fix

scopeCache() now captures each store's original path/lock_path on bootstrap() and scopes those instead of a hardcoded default. scopeCachePath() handles the actual scoping:

  • If the configured path is under the central storage path, that central part gets swapped for the tenant's storage path, keeping everything after it the same (e.g. storage/framework/cache/data becomes storage/tenant1/framework/cache/data).
  • If the path is outside the central storage path (e.g. a store pointed at a shared network mount), there's nothing to swap, so the tenant's suffix just gets appended to the end of the path instead.

lock_path stays null when a store doesn't configure it, rather than us making it default to the scoped path -- FileStore already falls back to path for locks in that case, so no need for us to do that, we can just respect the store's original config.

Possible further improvement

There's another thing we could explore -- rather than scopeCachePath() hardcoding how a store's path gets scoped, that could go through config instead, similar to how diskRoot() resolves root_override templates with the %storage_path%/%tenant% placeholders. Something like a tenancy.cache.path_override.{$name} key with a %configured_path%/%suffix%-style template.

That would make cache path scoping configurable instead of hardcoded, and it'd let people do things
this fix doesn't support -- e.g. keep the cache directory central, with tenant subdirectories under it, instead of moving the whole store under the tenant's storage path. I think that'd help specifically when people set suffix_storage_path => false, since with that setting, storage_path() never gets a tenant prefix anywhere, but scopeCache() doesn't check it, so it still creates a storage/tenant1/framework/cache/data directory (the only tenant-prefixed thing anywhere under storage/ for those users).

Looked into that for a bit, but not sure if that's worth pursuing right now. This fix already swaps the central path prefix for the tenant's when a store's path is under storage_path(), and appends the suffix otherwise, without needing any config. A path_override key would only be useful for less cases like the "central with subdirectory" example mentioned above -- stores that don't set one would just keep using this fix's existing behavior directly (no override or template involved).

Leaving this as a draft for now, until this alternative is explored and discussed thoroughly.

More issues found by looking into the possible improvements

'suffix_storage_path' => false is not respected at all by scopeCache and scopeSessions.

(will edit this, for now, see https://3.basecamp.com/5170965/buckets/23651018/todos/10144092860#__recording_10154592928)

  • Delete the notes about regression in the tests after reviewing the PR fully
  • Decide whether we should add the path_override thing

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes

    • Improved tenant isolation for file-based cache stores.
    • Preserved central cache entries when leaving a tenant context.
    • Kept independently configured cache and lock directories separate.
    • Correctly handled custom cache paths and optional lock paths.
    • Ensured cache paths outside central storage remain properly isolated.
  • Tests

    • Added coverage for tenant cache isolation, central cache persistence, custom directory configurations, independent lock paths, and cache behavior after tenancy ends.

The tests cover the current (mostly incorrect) scopeCache() behavior (= hardcoding the /framework/cache/data path regardless of what was configured).

The 'file cache stores are separated per tenant' is not a regression test -- it covers the default path, which already worked correctly, there were just no tests for it. The rest are regression tests (see the "NOTE ABOUT REGRESSION" comments -- these are temporary, added them just so that it's clear what's currently wrong or broken) that should be fixed by the FS bootstrapper fix in the next commit.
scopeCache() rewrote path and lock_path for every file-driver store to a hardcoded '<storage>/framework/cache/data' path, completely ignoring the store's config. Now, scopeCache() remembers each store's original path and lock_path, scopes these paths for the tenant, and restores them to the stored originals on revert.

The store's lock_path was always overwritten by the same hardcoded path. But lock_path is configurable too, AND it's actually optional (unlike path). If it's not configured at all (= it's null or just unset), Laravel automatically falls back to the store's path. So in that case, leave lock_path null instead of assigning the path to it. This is not a *huge* change, assigning path to lock_path would essentially achieve the same thing, BUT if someone explicitly sets lock_path to null in the config, we should just respect that and let Laravel fall back to the path instead of setting the lock_path ourselves.

Also, on revert(), the same hardcoded path was used in scopeCache(). So if someone used a custom file driver-based store, cached something in central context, initialized and ended tenancy, the central cache got corrupt (see the 'central cache is not lost when tenancy ends' test).
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 41fd96c7-a332-47fe-9382-ce8b813f57c4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

File cache stores now retain their original paths, apply tenant-specific path and lock_path values independently, update FileStore instances, and restore central paths after tenancy ends. Tests cover isolation, restoration, custom paths, and lock handling.

Changes

Filesystem cache scoping

Layer / File(s) Summary
Per-store cache path scoping
src/Bootstrappers/FilesystemTenancyBootstrapper.php
Captures original store paths, scopes configured and non-storage paths per tenant, and applies matching directories to file stores.
Cache isolation and restoration coverage
tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php
Tests tenant isolation, central cache persistence, separate stores, independent lock paths, missing lock paths, and custom cache locations.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Poem

A rabbit guards each cache burrow,
Tenant paths stay out of sorrow.
Lock paths follow, then central ones
Return when tenant work is done.
Hop, hop—files stay apart!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main bug fix: FilesystemTenancyBootstrapper::scopeCache() no longer discards configured cache paths. This matches the core change documented in the PR objectives.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch scope-cache-fix

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.

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.65%. Comparing base (553f57a) to head (aabba92).

Files with missing lines Patch % Lines
...rc/Bootstrappers/FilesystemTenancyBootstrapper.php 90.90% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1473      +/-   ##
============================================
+ Coverage     86.63%   86.65%   +0.02%     
- Complexity     1219     1227       +8     
============================================
  Files           186      186              
  Lines          3583     3605      +22     
============================================
+ Hits           3104     3124      +20     
- Misses          479      481       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lukinovec

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@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: 1

🤖 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 `@src/Bootstrappers/FilesystemTenancyBootstrapper.php`:
- Around line 220-244: Validate the original cache path in the cache-scoping
flow before passing it to scopeCachePath(); when a file-driver store omits path,
fail with a clear configuration error or skip the store consistently during
bootstrap and revert. Preserve the existing optional lock_path handling and
ensure scopeCachePath() is never called with null.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 31b4c918-c01e-4d38-bdde-b69db2e32054

📥 Commits

Reviewing files that changed from the base of the PR and between 553f57a and 483a3ec.

📒 Files selected for processing (2)
  • src/Bootstrappers/FilesystemTenancyBootstrapper.php
  • tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php

Comment thread src/Bootstrappers/FilesystemTenancyBootstrapper.php Outdated
scopeCache() didn't feel right since it 1) stored thee original paths, 2) actually scoped things. Separate the concerns so that scopeCache() just does that -- scopes cache.
@lukinovec

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

Making it protected could be a minor bc, and it'd be inconsistent with scopeSessions (which is public).
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.

1 participant