Repository: kopia/kopia
Pull request #2619 (2022-12-02 23:03:19)

build(deps): bump cloud.google.com/go/storage from 1.28.0 to 1.28.1
Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.28.0 to 1.28.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/googleapis/google-cloud-go/releases">cloud.google.com/go/storage's releases</a>.</em></p>
<blockquote>
<h2>storage: v1.28.1</h2>
<h2><a href="https://github.com/googleapis/google-cloud-go/compare/storage/v1.28.0...storage/v1.28.1">1.28.1</a> (2022-12-02)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>storage:</strong> downgrade some dependencies (<a href="https://github.com/googleapis/google-cloud-go/commit/754015236d5af7c82a75da218b71a87b9ead6eb5">7540152</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/googleapis/google-cloud-go/commit/3756ec80e8575c3501addee5b859e0782ff1fe9d"><code>3756ec8</code></a> chore(main): release storage 1.28.1 (<a href="https://github-redirect.dependabot.com/googleapis/google-cloud-go/issues/7119">#7119</a>)</li>
<li><a href="https://github.com/googleapis/google-cloud-go/commit/18bb342e8eb633f6bbee3561a6f4359dae81ed2f"><code>18bb342</code></a> chore(main): release pubsub 1.27.1 (<a href="https://github-redirect.dependabot.com/googleapis/google-cloud-go/issues/7118">#7118</a>)</li>
<li><a href="https://github.com/googleapis/google-cloud-go/commit/bd7b5a731bd0d447d97662781dc4c4992cdcc30f"><code>bd7b5a7</code></a> chore(main): release logging 1.6.1 (<a href="https://github-redirect.dependabot.com/googleapis/google-cloud-go/issues/7120">#7120</a>)</li>
<li><a href="https://github.com/googleapis/google-cloud-go/commit/26e3cdfc016d101bd5299c3fa39e1444b572594c"><code>26e3cdf</code></a> chore(main): release profiler 0.3.1 (<a href="https://github-redirect.dependabot.com/googleapis/google-cloud-go/issues/7121">#7121</a>)</li>
<li><a href="https://github.com/googleapis/google-cloud-go/commit/754015236d5af7c82a75da218b71a87b9ead6eb5"><code>7540152</code></a> chore: downgrade some dependencies (<a href="https://github-redirect.dependabot.com/googleapis/google-cloud-go/issues/7117">#7117</a>)</li>
<li><a href="https://github.com/googleapis/google-cloud-go/commit/10f95e386dc865e731614ffb13d890bb550c1eed"><code>10f95e3</code></a> chore(main): release pubsublite 1.5.0 (<a href="https://github-redirect.dependabot.com/googleapis/google-cloud-go/issues/7064">#7064</a>)</li>
<li><a href="https://github.com/googleapis/google-cloud-go/commit/e648bd95ff5b33383440e18245106741292ac97a"><code>e648bd9</code></a> feat(pubsublite): set finalizer for PublisherClient (<a href="https://github-redirect.dependabot.com/googleapis/google-cloud-go/issues/7109">#7109</a>)</li>
<li><a href="https://github.com/googleapis/google-cloud-go/commit/ce0c9440634b17558adea724ea4e5a735dc1f1d0"><code>ce0c944</code></a> chore: release main (<a href="https://github-redirect.dependabot.com/googleapis/google-cloud-go/issues/7063">#7063</a>)</li>
<li><a href="https://github.com/googleapis/google-cloud-go/commit/9fb390d3755dc103c80d0c87113abb5fbebd192e"><code>9fb390d</code></a> chore(main): release spanner 1.41.0 (<a href="https://github-redirect.dependabot.com/googleapis/google-cloud-go/issues/7068">#7068</a>)</li>
<li><a href="https://github.com/googleapis/google-cloud-go/commit/7231644e71f05abc864924a0065b9ea22a489180"><code>7231644</code></a> chore(all): auto-regenerate gapics (<a href="https://github-redirect.dependabot.com/googleapis/google-cloud-go/issues/7110">#7110</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/googleapis/google-cloud-go/compare/spanner/v1.28.0...storage/v1.28.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=cloud.google.com/go/storage&package-manager=go_modules&previous-version=1.28.0&new-version=1.28.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
Dependabot will merge this PR once CI passes on it, as requested by @kopia-builder.

[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

codecov[bot]: # [Codecov](https://codecov.io/gh/kopia/kopia/pull/2619?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) Report
Base: **74.92**% // Head: **74.93**% // Increases project coverage by **`+0.00%`** :tada:
> Coverage data is based on head [(`f428378`)](https://codecov.io/gh/kopia/kopia/pull/2619?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) compared to base [(`8978705`)](https://codecov.io/gh/kopia/kopia/commit/89787057de08b8d279411d33d30f345ed0387cc1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia).
> Patch has no changes to coverable lines.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #2619   +/-   ##
=======================================
  Coverage   74.92%   74.93%           
=======================================
  Files         453      453           
  Lines       35541    35541           
=======================================
+ Hits        26629    26631    +2     
+ Misses       7027     7026    -1     
+ Partials     1885     1884    -1     
```


| [Impacted Files](https://codecov.io/gh/kopia/kopia/pull/2619?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) | Coverage Δ | |
|---|---|---|
| [repo/content/content\_manager.go](https://codecov.io/gh/kopia/kopia/pull/2619/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-cmVwby9jb250ZW50L2NvbnRlbnRfbWFuYWdlci5nbw==) | `83.58% <0.00%> (-0.50%)` | :arrow_down: |
| [internal/providervalidation/providervalidation.go](https://codecov.io/gh/kopia/kopia/pull/2619/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-aW50ZXJuYWwvcHJvdmlkZXJ2YWxpZGF0aW9uL3Byb3ZpZGVydmFsaWRhdGlvbi5nbw==) | `65.68% <0.00%> (+0.73%)` | :arrow_up: |
| [internal/dirutil/mksubdirall.go](https://codecov.io/gh/kopia/kopia/pull/2619/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-aW50ZXJuYWwvZGlydXRpbC9ta3N1YmRpcmFsbC5nbw==) | `100.00% <0.00%> (+7.89%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/kopia/kopia/pull/2619?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia).


Patch:
diff --git a/app/package-lock.json b/app/package-lock.json
index 24ed5f58..d2a969cf 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -26,7 +26,7 @@
         "electron-builder": "^23.3.3",
         "electron-notarize": "^1.2.2",
         "playwright": "^1.26.1",
-        "playwright-core": "^1.26.0"
+        "playwright-core": "^1.28.1"
       }
     },
     "node_modules/@develar/schema-utils": {
@@ -211,6 +211,18 @@
         "node": ">=14"
       }
     },
+    "node_modules/@playwright/test/node_modules/playwright-core": {
+      "version": "1.26.1",
+      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.26.1.tgz",
+      "integrity": "sha512-hzFchhhxnEiPc4qVPs9q2ZR+5eKNifY2hQDHtg1HnTTUuphYCBP8ZRb2si+B1TR7BHirgXaPi48LIye5SgrLAA==",
+      "dev": true,
+      "bin": {
+        "playwright": "cli.js"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/@sindresorhus/is": {
       "version": "0.14.0",
       "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
@@ -2829,6 +2841,18 @@
       }
     },
     "node_modules/playwright-core": {
+      "version": "1.28.1",
+      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz",
+      "integrity": "sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==",
+      "dev": true,
+      "bin": {
+        "playwright": "cli.js"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/playwright/node_modules/playwright-core": {
       "version": "1.26.1",
       "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.26.1.tgz",
       "integrity": "sha512-hzFchhhxnEiPc4qVPs9q2ZR+5eKNifY2hQDHtg1HnTTUuphYCBP8ZRb2si+B1TR7BHirgXaPi48LIye5SgrLAA==",
@@ -3859,6 +3883,14 @@
       "requires": {
         "@types/node": "*",
         "playwright-core": "1.26.1"
+      },
+      "dependencies": {
+        "playwright-core": {
+          "version": "1.26.1",
+          "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.26.1.tgz",
+          "integrity": "sha512-hzFchhhxnEiPc4qVPs9q2ZR+5eKNifY2hQDHtg1HnTTUuphYCBP8ZRb2si+B1TR7BHirgXaPi48LIye5SgrLAA==",
+          "dev": true
+        }
       }
     },
     "@sindresorhus/is": {
@@ -5906,12 +5938,20 @@
       "dev": true,
       "requires": {
         "playwright-core": "1.26.1"
+      },
+      "dependencies": {
+        "playwright-core": {
+          "version": "1.26.1",
+          "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.26.1.tgz",
+          "integrity": "sha512-hzFchhhxnEiPc4qVPs9q2ZR+5eKNifY2hQDHtg1HnTTUuphYCBP8ZRb2si+B1TR7BHirgXaPi48LIye5SgrLAA==",
+          "dev": true
+        }
       }
     },
     "playwright-core": {
-      "version": "1.26.1",
-      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.26.1.tgz",
-      "integrity": "sha512-hzFchhhxnEiPc4qVPs9q2ZR+5eKNifY2hQDHtg1HnTTUuphYCBP8ZRb2si+B1TR7BHirgXaPi48LIye5SgrLAA==",
+      "version": "1.28.1",
+      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz",
+      "integrity": "sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==",
       "dev": true
     },
     "plist": {
diff --git a/app/package.json b/app/package.json
index 076d632a..82677f45 100644
--- a/app/package.json
+++ b/app/package.json
@@ -118,7 +118,7 @@
     "electron-builder": "^23.3.3",
     "electron-notarize": "^1.2.2",
     "playwright": "^1.26.1",
-    "playwright-core": "^1.26.0"
+    "playwright-core": "^1.28.1"
   },
   "homepage": "./",
   "description": "Fast and secure open source backup.",
diff --git a/go.mod b/go.mod
index 593f04db..b4bc22cb 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/kopia/kopia
 go 1.19
 
 require (
-	cloud.google.com/go/storage v1.28.0
+	cloud.google.com/go/storage v1.28.1
 	github.com/Azure/azure-pipeline-go v0.2.3 // indirect
 	github.com/Azure/azure-storage-blob-go v0.15.0
 	github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 // indirect
@@ -98,7 +98,7 @@ require (
 	go.uber.org/multierr v1.8.0
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect
+	google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c // indirect
 )
 
 require (
@@ -118,7 +118,7 @@ require (
 require (
 	cloud.google.com/go/compute v1.12.1 // indirect
 	cloud.google.com/go/compute/metadata v0.2.1 // indirect
-	cloud.google.com/go/iam v0.6.0 // indirect
+	cloud.google.com/go/iam v0.7.0 // indirect
 	github.com/alessio/shellescape v1.4.1 // indirect
 	github.com/chromedp/sysutil v1.0.0 // indirect
 	github.com/go-logr/logr v1.2.3 // indirect
diff --git a/go.sum b/go.sum
index 8f38343f..f0bbd5a4 100644
--- a/go.sum
+++ b/go.sum
@@ -27,9 +27,9 @@ cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22m
 cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/iam v0.6.0 h1:nsqQC88kT5Iwlm4MeNGTpfMWddp6NB/UOLFTH6m1QfQ=
-cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=
-cloud.google.com/go/longrunning v0.1.1 h1:y50CXG4j0+qvEukslYFBCrzaXX0qpFbBzc3PchSu/LE=
+cloud.google.com/go/iam v0.7.0 h1:k4MuwOsS7zGJJ+QfZ5vBK8SgHBAvYN/23BWsiihJ1vs=
+cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg=
+cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -39,8 +39,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-cloud.google.com/go/storage v1.28.0 h1:DLrIZ6xkeZX6K70fU/boWx5INJumt6f+nwwWSHXzzGY=
-cloud.google.com/go/storage v1.28.0/go.mod h1:qlgZML35PXA3zoEnIkiPLY4/TOkUleufRlu6qmcf7sI=
+cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI=
+cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
 github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
@@ -721,8 +721,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
 google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo=
-google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=
+google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c h1:S34D59DS2GWOEwWNt4fYmTcFrtlOgukG2k9WsomZ7tg=
+google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
diff --git a/internal/cache/content_cache.go b/internal/cache/content_cache.go
index 74b5a5f0..c3d936eb 100644
--- a/internal/cache/content_cache.go
+++ b/internal/cache/content_cache.go
@@ -5,6 +5,7 @@
 
 	"github.com/pkg/errors"
 
+	"github.com/kopia/kopia/internal/cacheprot"
 	"github.com/kopia/kopia/internal/gather"
 	"github.com/kopia/kopia/internal/impossible"
 	"github.com/kopia/kopia/internal/metrics"
@@ -189,7 +190,7 @@ func NewContentCache(ctx context.Context, st blob.Storage, opt Options, mr *metr
 		}
 	}
 
-	pc, err := NewPersistentCache(ctx, opt.CacheSubDir, cacheStorage, ChecksumProtection(opt.HMACSecret), opt.Sweep, mr)
+	pc, err := NewPersistentCache(ctx, opt.CacheSubDir, cacheStorage, cacheprot.ChecksumProtection(opt.HMACSecret), opt.Sweep, mr)
 	if err != nil {
 		return nil, errors.Wrap(err, "unable to create base cache")
 	}
diff --git a/internal/cache/persistent_lru_cache.go b/internal/cache/persistent_lru_cache.go
index 6b539fab..09d0f8af 100644
--- a/internal/cache/persistent_lru_cache.go
+++ b/internal/cache/persistent_lru_cache.go
@@ -11,6 +11,7 @@
 	lru "github.com/hashicorp/golang-lru"
 	"github.com/pkg/errors"
 
+	"github.com/kopia/kopia/internal/cacheprot"
 	"github.com/kopia/kopia/internal/clock"
 	"github.com/kopia/kopia/internal/ctxutil"
 	"github.com/kopia/kopia/internal/gather"
@@ -42,7 +43,7 @@ type PersistentCache struct {
 	anyChange atomic.Bool
 
 	cacheStorage      Storage
-	storageProtection StorageProtection
+	storageProtection cacheprot.StorageProtection
 	sweep             SweepSettings
 
 	description string
@@ -137,7 +138,7 @@ func (c *PersistentCache) GetPartial(ctx context.Context, key string, offset, le
 		prot := c.storageProtection
 		if length >= 0 {
 			// only full items have protection.
-			prot = nullStorageProtection{}
+			prot = cacheprot.NoProtection()
 		}
 
 		if err := prot.Verify(key, tmp.Bytes(), output); err == nil {
@@ -333,7 +334,7 @@ func (s SweepSettings) applyDefaults() SweepSettings {
 }
 
 // NewPersistentCache creates the persistent cache in the provided storage.
-func NewPersistentCache(ctx context.Context, description string, cacheStorage Storage, storageProtection StorageProtection, sweep SweepSettings, mr *metrics.Registry) (*PersistentCache, error) {
+func NewPersistentCache(ctx context.Context, description string, cacheStorage Storage, storageProtection cacheprot.StorageProtection, sweep SweepSettings, mr *metrics.Registry) (*PersistentCache, error) {
 	if cacheStorage == nil {
 		return nil, nil
 	}
@@ -341,7 +342,7 @@ func NewPersistentCache(ctx context.Context, description string, cacheStorage St
 	sweep = sweep.applyDefaults()
 
 	if storageProtection == nil {
-		storageProtection = NoProtection()
+		storageProtection = cacheprot.NoProtection()
 	}
 
 	c := &PersistentCache{
diff --git a/internal/cache/storage_protection.go b/internal/cacheprot/storage_protection.go
similarity index 97%
rename from internal/cache/storage_protection.go
rename to internal/cacheprot/storage_protection.go
index 947a45c7..77658215 100644
--- a/internal/cache/storage_protection.go
+++ b/internal/cacheprot/storage_protection.go
@@ -1,4 +1,5 @@
-package cache
+// Package cacheprot provides utilities for protection of cache entries.
+package cacheprot
 
 import (
 	"crypto/sha256"
diff --git a/repo/api_server_repository.go b/repo/api_server_repository.go
index 7f4c68d3..520dbc9d 100644
--- a/repo/api_server_repository.go
+++ b/repo/api_server_repository.go
@@ -35,7 +35,7 @@ type apiServerRepository struct {
 	omgr                             *object.Manager
 	wso                              WriteSessionOptions
 
-	immutableServerRepositoryParameters // immutable parameters
+	*immutableServerRepositoryParameters // immutable parameters
 }
 
 func (r *apiServerRepository) APIServerURL() string {
@@ -132,7 +132,19 @@ func (r *apiServerRepository) Refresh(ctx context.Context) error {
 }
 
 func (r *apiServerRepository) Flush(ctx context.Context) error {
-	return errors.Wrap(r.cli.Post(ctx, "flush", nil, nil), "Flush")
+	if err := invokeCallbacks(ctx, r, r.beforeFlush); err != nil {
+		return errors.Wrap(err, "before flush")
+	}
+
+	if err := r.cli.Post(ctx, "flush", nil, nil); err != nil {
+		return errors.Wrap(err, "Flush")
+	}
+
+	if err := invokeCallbacks(ctx, r, r.afterFlush); err != nil {
+		return errors.Wrap(err, "after flush")
+	}
+
+	return nil
 }
 
 func (r *apiServerRepository) SupportsContentCompression() (bool, error) {
@@ -153,6 +165,10 @@ func (r *apiServerRepository) NewWriter(ctx context.Context, opt WriteSessionOpt
 	w.omgr = omgr
 	w.wso = opt
 
+	if w.wso.OnUpload == nil {
+		w.wso.OnUpload = func(i int64) {}
+	}
+
 	r.addRef()
 
 	return ctx, w, nil
@@ -258,7 +274,7 @@ func (r *apiServerRepository) PrefetchContents(ctx context.Context, contentIDs [
 var _ Repository = (*apiServerRepository)(nil)
 
 // openRestAPIRepository connects remote repository over Kopia API.
-func openRestAPIRepository(ctx context.Context, si *APIServerInfo, password string, par immutableServerRepositoryParameters) (Repository, error) {
+func openRestAPIRepository(ctx context.Context, si *APIServerInfo, password string, par *immutableServerRepositoryParameters) (Repository, error) {
 	cli, err := apiclient.NewKopiaAPIClient(apiclient.Options{
 		BaseURL:                             si.BaseURL,
 		TrustedServerCertificateFingerprint: si.TrustedServerCertificateFingerprint,
diff --git a/repo/content/committed_read_manager.go b/repo/content/committed_read_manager.go
index 9800bf82..1aa99fa7 100644
--- a/repo/content/committed_read_manager.go
+++ b/repo/content/committed_read_manager.go
@@ -11,6 +11,7 @@
 	"go.uber.org/zap"
 
 	"github.com/kopia/kopia/internal/cache"
+	"github.com/kopia/kopia/internal/cacheprot"
 	"github.com/kopia/kopia/internal/clock"
 	"github.com/kopia/kopia/internal/epoch"
 	"github.com/kopia/kopia/internal/gather"
@@ -427,7 +428,7 @@ func (sm *SharedManager) setupReadManagerCaches(ctx context.Context, caching *Ca
 		return errors.Wrap(err, "unable to initialize index blob cache storage")
 	}
 
-	indexBlobCache, err := cache.NewPersistentCache(ctx, "index-blobs", indexBlobStorage, cache.ChecksumProtection(caching.HMACSecret), cache.SweepSettings{
+	indexBlobCache, err := cache.NewPersistentCache(ctx, "index-blobs", indexBlobStorage, cacheprot.ChecksumProtection(caching.HMACSecret), cache.SweepSettings{
 		MaxSizeBytes: metadataCacheSize,
 		MinSweepAge:  caching.MinMetadataSweepAge.DurationOrDefault(DefaultMetadataCacheSweepAge),
 	}, mr)
diff --git a/repo/grpc_repository_client.go b/repo/grpc_repository_client.go
index f3bdf9a9..20994a84 100644
--- a/repo/grpc_repository_client.go
+++ b/repo/grpc_repository_client.go
@@ -73,7 +73,7 @@ type grpcRepositoryClient struct {
 
 	asyncWritesWG *errgroup.Group
 
-	immutableServerRepositoryParameters
+	*immutableServerRepositoryParameters
 
 	serverSupportsContentCompression bool
 	omgr                             *object.Manager
@@ -422,11 +422,21 @@ func (r *grpcRepositoryClient) Flush(ctx context.Context) error {
 		return errors.Wrap(err, "error waiting for async writes")
 	}
 
-	_, err := inSessionWithoutRetry(ctx, r, func(ctx context.Context, sess *grpcInnerSession) (bool, error) {
+	if err := invokeCallbacks(ctx, r, r.beforeFlush); err != nil {
+		return errors.Wrap(err, "before flush")
+	}
+
+	if _, err := inSessionWithoutRetry(ctx, r, func(ctx context.Context, sess *grpcInnerSession) (bool, error) {
 		return false, sess.Flush(ctx)
-	})
+	}); err != nil {
+		return err
+	}
 
-	return err
+	if err := invokeCallbacks(ctx, r, r.afterFlush); err != nil {
+		return errors.Wrap(err, "after flush")
+	}
+
+	return nil
 }
 
 func (r *grpcInnerSession) Flush(ctx context.Context) error {
@@ -743,7 +753,7 @@ func (c grpcCreds) RequireTransportSecurity() bool {
 
 // openGRPCAPIRepository opens the Repository based on remote GRPC server.
 // The APIServerInfo must have the address of the repository as 'https://host:port'
-func openGRPCAPIRepository(ctx context.Context, si *APIServerInfo, password string, par immutableServerRepositoryParameters) (Repository, error) {
+func openGRPCAPIRepository(ctx context.Context, si *APIServerInfo, password string, par *immutableServerRepositoryParameters) (Repository, error) {
 	var transportCreds credentials.TransportCredentials
 
 	if si.TrustedServerCertificateFingerprint != "" {
@@ -852,7 +862,7 @@ func newGRPCAPIRepositoryForConnection(
 	conn *grpc.ClientConn,
 	opt WriteSessionOptions,
 	transparentRetries bool,
-	par immutableServerRepositoryParameters,
+	par *immutableServerRepositoryParameters,
 ) (*grpcRepositoryClient, error) {
 	if opt.OnUpload == nil {
 		opt.OnUpload = func(i int64) {}
diff --git a/repo/open.go b/repo/open.go
index d44e73cf..888325f1 100644
--- a/repo/open.go
+++ b/repo/open.go
@@ -13,6 +13,7 @@
 	"golang.org/x/crypto/scrypt"
 
 	"github.com/kopia/kopia/internal/cache"
+	"github.com/kopia/kopia/internal/cacheprot"
 	"github.com/kopia/kopia/internal/epoch"
 	"github.com/kopia/kopia/internal/feature"
 	"github.com/kopia/kopia/internal/metrics"
@@ -64,11 +65,13 @@
 
 // Options provides configuration parameters for connection to a repository.
 type Options struct {
-	TraceStorage        bool             // Logs all storage access using provided Printf-style function
-	TimeNowFunc         func() time.Time // Time provider
-	DisableInternalLog  bool             // Disable internal log
-	UpgradeOwnerID      string           // Owner-ID of any upgrade in progress, when this is not set the access may be restricted
-	DoNotWaitForUpgrade bool             // Disable the exponential forever backoff on an upgrade lock.
+	TraceStorage        bool                        // Logs all storage access using provided Printf-style function
+	TimeNowFunc         func() time.Time            // Time provider
+	DisableInternalLog  bool                        // Disable internal log
+	UpgradeOwnerID      string                      // Owner-ID of any upgrade in progress, when this is not set the access may be restricted
+	DoNotWaitForUpgrade bool                        // Disable the exponential forever backoff on an upgrade lock.
+	BeforeFlush         []RepositoryWriterCallbacks // list of callbacks to invoke before every flush
+	AfterFlush          []RepositoryWriterCallbacks // list of callbacks to invoke after every flush
 
 	OnFatalError func(err error) // function to invoke when repository encounters a fatal error, usually invokes os.Exit
 
@@ -119,7 +122,7 @@ func Open(ctx context.Context, configFile, password string, options *Options) (r
 	}
 
 	if lc.APIServer != nil {
-		return OpenAPIServer(ctx, lc.APIServer, lc.ClientOptions, lc.Caching, password)
+		return openAPIServer(ctx, lc.APIServer, lc.ClientOptions, lc.Caching, password, options)
 	}
 
 	return openDirect(ctx, configFile, lc, password, options)
@@ -143,7 +146,7 @@ func getContentCacheOrNil(ctx context.Context, opt *content.CachingOptions, pass
 		return nil, errors.Wrap(err, "unable to derive cache encryption key from password")
 	}
 
-	prot, err := cache.AuthenticatedEncryptionProtection(cacheEncryptionKey)
+	prot, err := cacheprot.AuthenticatedEncryptionProtection(cacheEncryptionKey)
 	if err != nil {
 		return nil, errors.Wrap(err, "unable to initialize protection")
 	}
@@ -159,8 +162,10 @@ func getContentCacheOrNil(ctx context.Context, opt *content.CachingOptions, pass
 	return pc, nil
 }
 
-// OpenAPIServer connects remote repository over Kopia API.
-func OpenAPIServer(ctx context.Context, si *APIServerInfo, cliOpts ClientOptions, cachingOptions *content.CachingOptions, password string) (Repository, error) {
+// openAPIServer connects remote repository over Kopia API.
+func openAPIServer(ctx context.Context, si *APIServerInfo, cliOpts ClientOptions, cachingOptions *content.CachingOptions, password string, options *Options) (Repository, error) {
+	cachingOptions = cachingOptions.CloneOrDefault()
+
 	mr := metrics.NewRegistry()
 
 	contentCache, err := getContentCacheOrNil(ctx, cachingOptions, password, mr)
@@ -179,11 +184,13 @@ func(ctx context.Context) error {
 		mr.Close,
 	)
 
-	par := immutableServerRepositoryParameters{
+	par := &immutableServerRepositoryParameters{
 		cliOpts:          cliOpts,
 		contentCache:     contentCache,
 		metricsRegistry:  mr,
 		refCountedCloser: closer,
+		beforeFlush:      options.BeforeFlush,
+		afterFlush:       options.AfterFlush,
 	}
 
 	if si.DisableGRPC {
@@ -345,6 +352,8 @@ func openWithConfig(ctx context.Context, st blob.Storage, cliOpts ClientOptions,
 			throttler:        throttler,
 			metricsRegistry:  mr,
 			refCountedCloser: closer,
+			beforeFlush:      options.BeforeFlush,
+			afterFlush:       options.AfterFlush,
 		},
 	}
 
diff --git a/repo/repository.go b/repo/repository.go
index ad60b847..58105eff 100644
--- a/repo/repository.go
+++ b/repo/repository.go
@@ -97,10 +97,25 @@ type immutableDirectRepositoryParameters struct {
 	nextWriterID    *int32
 	throttler       throttling.SettableThrottler
 	metricsRegistry *metrics.Registry
+	beforeFlush     []RepositoryWriterCallbacks
+	afterFlush      []RepositoryWriterCallbacks
 
 	*refCountedCloser
 }
 
+// RepositoryWriterCallbacks is a hook function invoked before and after each flush.
+type RepositoryWriterCallbacks func(ctx context.Context, w RepositoryWriter) error
+
+func invokeCallbacks(ctx context.Context, w RepositoryWriter, callbacks []RepositoryWriterCallbacks) error {
+	for _, h := range callbacks {
+		if err := h(ctx, w); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // directRepository is an implementation of repository that directly manipulates underlying storage.
 type directRepository struct {
 	immutableDirectRepositoryParameters
@@ -277,11 +292,23 @@ func (r *directRepository) NewDirectWriter(ctx context.Context, opt WriteSession
 
 // Flush waits for all in-flight writes to complete.
 func (r *directRepository) Flush(ctx context.Context) error {
+	if err := invokeCallbacks(ctx, r, r.beforeFlush); err != nil {
+		return errors.Wrap(err, "before flush")
+	}
+
 	if err := r.mmgr.Flush(ctx); err != nil {
 		return errors.Wrap(err, "error flushing manifests")
 	}
 
-	return errors.Wrap(r.cmgr.Flush(ctx), "error flushing contents")
+	if err := r.cmgr.Flush(ctx); err != nil {
+		return errors.Wrap(err, "error flushing contents")
+	}
+
+	if err := invokeCallbacks(ctx, r, r.afterFlush); err != nil {
+		return errors.Wrap(err, "after flush")
+	}
+
+	return nil
 }
 
 // Metrics provides access to metrics registry.
diff --git a/repo/server_repository_params.go b/repo/server_repository_params.go
index cd870d37..5cb7e8f1 100644
--- a/repo/server_repository_params.go
+++ b/repo/server_repository_params.go
@@ -14,6 +14,9 @@ type immutableServerRepositoryParameters struct {
 	cliOpts         ClientOptions
 	metricsRegistry *metrics.Registry
 	contentCache    *cache.PersistentCache
+	beforeFlush     []RepositoryWriterCallbacks
+	afterFlush      []RepositoryWriterCallbacks
+
 	*refCountedCloser
 }
 


Test patch:
diff --git a/internal/cache/persistent_lru_cache_test.go b/internal/cache/persistent_lru_cache_test.go
index 24dec137..76ee7ee5 100644
--- a/internal/cache/persistent_lru_cache_test.go
+++ b/internal/cache/persistent_lru_cache_test.go
@@ -11,6 +11,7 @@
 
 	"github.com/kopia/kopia/internal/blobtesting"
 	"github.com/kopia/kopia/internal/cache"
+	"github.com/kopia/kopia/internal/cacheprot"
 	"github.com/kopia/kopia/internal/fault"
 	"github.com/kopia/kopia/internal/gather"
 	"github.com/kopia/kopia/internal/testlogging"
@@ -25,7 +26,7 @@ func TestPersistentLRUCache(t *testing.T) {
 
 	cs := blobtesting.NewMapStorage(blobtesting.DataMap{}, nil, nil).(cache.Storage)
 
-	pc, err := cache.NewPersistentCache(ctx, "testing", cs, cache.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{
+	pc, err := cache.NewPersistentCache(ctx, "testing", cs, cacheprot.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{
 		MaxSizeBytes:   maxSizeBytes,
 		TouchThreshold: cache.DefaultTouchThreshold,
 		SweepFrequency: cache.DefaultSweepFrequency,
@@ -67,7 +68,7 @@ func TestPersistentLRUCache(t *testing.T) {
 	verifyBlobExists(ctx, t, cs, "key3")
 	verifyBlobExists(ctx, t, cs, "key4")
 
-	pc, err = cache.NewPersistentCache(ctx, "testing", cs, cache.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{
+	pc, err = cache.NewPersistentCache(ctx, "testing", cs, cacheprot.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{
 		MaxSizeBytes:   maxSizeBytes,
 		TouchThreshold: cache.DefaultTouchThreshold,
 		SweepFrequency: cache.DefaultSweepFrequency,
@@ -81,7 +82,7 @@ func TestPersistentLRUCache(t *testing.T) {
 
 	// create another persistent cache based on the same storage but wrong protection key.
 	// all reads from cache will be invalid, which means GetOrLoad will fetch them from the source.
-	pc2, err := cache.NewPersistentCache(ctx, "testing", cs, cache.ChecksumProtection([]byte{3, 2, 1}), cache.SweepSettings{
+	pc2, err := cache.NewPersistentCache(ctx, "testing", cs, cacheprot.ChecksumProtection([]byte{3, 2, 1}), cache.SweepSettings{
 		MaxSizeBytes:   maxSizeBytes,
 		TouchThreshold: cache.DefaultTouchThreshold,
 		SweepFrequency: cache.DefaultSweepFrequency,
@@ -153,7 +154,7 @@ func TestPersistentLRUCache_GetDeletesInvalidBlob(t *testing.T) {
 	fs := blobtesting.NewFaultyStorage(st)
 	fc := faultyCache{fs}
 
-	pc, err := cache.NewPersistentCache(ctx, "test", fc, cache.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{}, nil)
+	pc, err := cache.NewPersistentCache(ctx, "test", fc, cacheprot.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{}, nil)
 	require.NoError(t, err)
 
 	pc.Put(ctx, "key", gather.FromSlice([]byte{1, 2, 3}))
@@ -183,7 +184,7 @@ func TestPersistentLRUCache_PutIgnoresStorageFailure(t *testing.T) {
 	fs := blobtesting.NewFaultyStorage(st)
 	fc := faultyCache{fs}
 
-	pc, err := cache.NewPersistentCache(ctx, "test", fc, cache.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{}, nil)
+	pc, err := cache.NewPersistentCache(ctx, "test", fc, cacheprot.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{}, nil)
 	require.NoError(t, err)
 
 	fs.AddFault(blobtesting.MethodPutBlob).ErrorInstead(someError)
@@ -209,7 +210,7 @@ func TestPersistentLRUCache_SweepMinSweepAge(t *testing.T) {
 	fs := blobtesting.NewFaultyStorage(st)
 	fc := faultyCache{fs}
 
-	pc, err := cache.NewPersistentCache(ctx, "test", fc, cache.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{
+	pc, err := cache.NewPersistentCache(ctx, "test", fc, cacheprot.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{
 		SweepFrequency: 100 * time.Millisecond,
 		MaxSizeBytes:   1000,
 		MinSweepAge:    10 * time.Second,
@@ -238,7 +239,7 @@ func TestPersistentLRUCache_SweepIgnoresErrors(t *testing.T) {
 	fs := blobtesting.NewFaultyStorage(st)
 	fc := faultyCache{fs}
 
-	pc, err := cache.NewPersistentCache(ctx, "test", fc, cache.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{
+	pc, err := cache.NewPersistentCache(ctx, "test", fc, cacheprot.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{
 		SweepFrequency: 100 * time.Millisecond,
 		MaxSizeBytes:   1000,
 	}, nil)
@@ -271,7 +272,7 @@ func TestPersistentLRUCache_Sweep1(t *testing.T) {
 	fs := blobtesting.NewFaultyStorage(st)
 	fc := faultyCache{fs}
 
-	pc, err := cache.NewPersistentCache(ctx, "test", fc, cache.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{
+	pc, err := cache.NewPersistentCache(ctx, "test", fc, cacheprot.ChecksumProtection([]byte{1, 2, 3}), cache.SweepSettings{
 		SweepFrequency: 10 * time.Millisecond,
 		MaxSizeBytes:   1,
 		MinSweepAge:    0 * time.Second,
diff --git a/internal/cache/storage_protection_test.go b/internal/cacheprot/storage_protection_test.go
similarity index 76%
rename from internal/cache/storage_protection_test.go
rename to internal/cacheprot/storage_protection_test.go
index 4d57fce0..31d5ee11 100644
--- a/internal/cache/storage_protection_test.go
+++ b/internal/cacheprot/storage_protection_test.go
@@ -1,4 +1,4 @@
-package cache_test
+package cacheprot_test
 
 import (
 	"bytes"
@@ -6,27 +6,27 @@
 
 	"github.com/stretchr/testify/require"
 
-	"github.com/kopia/kopia/internal/cache"
+	"github.com/kopia/kopia/internal/cacheprot"
 	"github.com/kopia/kopia/internal/gather"
 )
 
 func TestNoStorageProection(t *testing.T) {
-	testStorageProtection(t, cache.NoProtection(), false)
+	testStorageProtection(t, cacheprot.NoProtection(), false)
 }
 
 func TestHMACStorageProtection(t *testing.T) {
-	testStorageProtection(t, cache.ChecksumProtection([]byte{1, 2, 3, 4}), true)
+	testStorageProtection(t, cacheprot.ChecksumProtection([]byte{1, 2, 3, 4}), true)
 }
 
 func TestEncryptionStorageProtection(t *testing.T) {
-	e, err := cache.AuthenticatedEncryptionProtection([]byte{1})
+	e, err := cacheprot.AuthenticatedEncryptionProtection([]byte{1})
 	require.NoError(t, err)
 
 	testStorageProtection(t, e, true)
 }
 
 //nolint:thelper
-func testStorageProtection(t *testing.T, sp cache.StorageProtection, protectsFromBitFlips bool) {
+func testStorageProtection(t *testing.T, sp cacheprot.StorageProtection, protectsFromBitFlips bool) {
 	payload := []byte{0, 1, 2, 3, 4}
 
 	var protected gather.WriteBuffer
diff --git a/repo/repository_test.go b/repo/repository_test.go
index 40ca7f11..37f46255 100644
--- a/repo/repository_test.go
+++ b/repo/repository_test.go
@@ -7,6 +7,7 @@
 	"math/rand"
 	"runtime/debug"
 	"strings"
+	"sync/atomic"
 	"testing"
 	"time"
 
@@ -17,7 +18,9 @@
 	"github.com/kopia/kopia/internal/epoch"
 	"github.com/kopia/kopia/internal/gather"
 	"github.com/kopia/kopia/internal/repotesting"
+	"github.com/kopia/kopia/internal/servertesting"
 	"github.com/kopia/kopia/internal/testlogging"
+	"github.com/kopia/kopia/internal/testutil"
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/blob"
 	"github.com/kopia/kopia/repo/blob/beforeop"
@@ -583,8 +586,22 @@ func TestObjectWritesWithRetention(t *testing.T) {
 	}))
 }
 
-func (s *formatSpecificTestSuite) TestWriteSessionFlushOnSuccess(t *testing.T) {
-	ctx, env := repotesting.NewEnvironment(t, s.formatVersion)
+func TestWriteSessionFlushOnSuccess(t *testing.T) {
+	var beforeFlushCount, afterFlushCount atomic.Int32
+
+	ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant, repotesting.Options{
+		OpenOptions: func(o *repo.Options) {
+			o.BeforeFlush = append(o.BeforeFlush, func(ctx context.Context, w repo.RepositoryWriter) error {
+				beforeFlushCount.Add(1)
+				return nil
+			})
+
+			o.AfterFlush = append(o.AfterFlush, func(ctx context.Context, w repo.RepositoryWriter) error {
+				afterFlushCount.Add(1)
+				return nil
+			})
+		},
+	})
 
 	var oid object.ID
 
@@ -593,7 +610,109 @@ func (s *formatSpecificTestSuite) TestWriteSessionFlushOnSuccess(t *testing.T) {
 		return nil
 	}))
 
+	require.EqualValues(t, 1, beforeFlushCount.Load())
+	require.EqualValues(t, 1, afterFlushCount.Load())
+
 	verify(ctx, t, env.Repository, oid, []byte{1, 2, 3}, "test-1")
+
+	someErr := errors.Errorf("some error")
+
+	require.ErrorIs(t, repo.WriteSession(ctx, env.Repository, repo.WriteSessionOptions{}, func(ctx context.Context, w repo.RepositoryWriter) error {
+		oid = writeObject(ctx, t, w, []byte{1, 2, 3, 4}, "test-2")
+		return someErr
+	}), someErr)
+
+	require.EqualValues(t, 1, beforeFlushCount.Load())
+	require.EqualValues(t, 1, afterFlushCount.Load())
+
+	require.ErrorIs(t, repo.WriteSession(ctx, env.Repository, repo.WriteSessionOptions{
+		FlushOnFailure: true,
+	}, func(ctx context.Context, w repo.RepositoryWriter) error {
+		oid = writeObject(ctx, t, w, []byte{1, 2, 3, 4, 5}, "test-3")
+		return someErr
+	}), someErr)
+
+	require.EqualValues(t, 2, beforeFlushCount.Load())
+	require.EqualValues(t, 2, afterFlushCount.Load())
+}
+
+func TestWriteSessionFlushOnSuccessClient_REST(t *testing.T) {
+	testWriteSessionFlushOnSuccessClient(t, true)
+}
+
+func TestWriteSessionFlushOnSuccessClient_GRPC(t *testing.T) {
+	testWriteSessionFlushOnSuccessClient(t, false)
+}
+
+//nolint:thelper
+func testWriteSessionFlushOnSuccessClient(t *testing.T, disableGRPC bool) {
+	ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant, repotesting.Options{})
+
+	apiServerInfo := servertesting.StartServer(t, env, true)
+	apiServerInfo.DisableGRPC = disableGRPC
+
+	var beforeFlushCount, afterFlushCount atomic.Int32
+
+	ctx2, cancel := context.WithCancel(testlogging.Context(t))
+	defer cancel()
+
+	rep, err := servertesting.ConnectAndOpenAPIServer(t, ctx2, apiServerInfo, repo.ClientOptions{
+		Username: servertesting.TestUsername,
+		Hostname: servertesting.TestHostname,
+	}, content.CachingOptions{
+		CacheDirectory: testutil.TempDirectory(t),
+	}, servertesting.TestPassword, &repo.Options{
+		BeforeFlush: []repo.RepositoryWriterCallbacks{
+			func(ctx context.Context, w repo.RepositoryWriter) error {
+				beforeFlushCount.Add(1)
+				return nil
+			},
+		},
+		AfterFlush: []repo.RepositoryWriterCallbacks{
+			func(ctx context.Context, w repo.RepositoryWriter) error {
+				afterFlushCount.Add(1)
+				return nil
+			},
+		},
+	})
+
+	defer rep.Close(ctx) //nolint:errcheck,staticcheck
+
+	require.NoError(t, err)
+
+	var oid object.ID
+
+	require.NoError(t, repo.WriteSession(ctx, rep, repo.WriteSessionOptions{}, func(ctx context.Context, w repo.RepositoryWriter) error {
+		oid = writeObject(ctx, t, w, []byte{1, 2, 3}, "test-1")
+		return nil
+	}))
+
+	require.EqualValues(t, 1, beforeFlushCount.Load())
+	require.EqualValues(t, 1, afterFlushCount.Load())
+
+	verify(ctx, t, rep, oid, []byte{1, 2, 3}, "test-1")
+
+	someErr := errors.Errorf("some error")
+
+	require.ErrorIs(t, repo.WriteSession(ctx, rep, repo.WriteSessionOptions{}, func(ctx context.Context, w repo.RepositoryWriter) error {
+		oid = writeObject(ctx, t, w, []byte{1, 2, 3, 4}, "test-2")
+		return someErr
+	}), someErr)
+
+	require.EqualValues(t, 1, beforeFlushCount.Load())
+	require.EqualValues(t, 1, afterFlushCount.Load())
+
+	require.ErrorIs(t, repo.WriteSession(ctx, rep, repo.WriteSessionOptions{
+		FlushOnFailure: true,
+	}, func(ctx context.Context, w repo.RepositoryWriter) error {
+		oid = writeObject(ctx, t, w, []byte{1, 2, 3, 4, 5}, "test-3")
+		return someErr
+	}), someErr)
+
+	t.Logf("-----")
+
+	require.EqualValues(t, 2, beforeFlushCount.Load())
+	require.EqualValues(t, 2, afterFlushCount.Load())
 }
 
 func (s *formatSpecificTestSuite) TestWriteSessionNoFlushOnFailure(t *testing.T) {