Summary
The ensureClientForBucket() pattern — resolve the state bucket's region via GetBucketLocation (cached) and rebuild a region-corrected S3 client reusing caller credentials without destroying the shared client — now exists in three near-identical copies:
This is pure duplication and a drift risk (a future fix to one copy can miss the others).
Proposed fix
Extract a shared helper, e.g. in src/utils/aws-region-resolver.ts:
async function rebuildClientForBucketRegion(client: S3Client, bucket: string): Promise<S3Client | null>
that encapsulates the probe (resolve creds + region via the cached resolveBucketRegion), the same-region short-circuit, and the credential-reusing rebuild (no .destroy() of the shared client). Each of the three call sites becomes a one-liner. Keep the per-call memoization (clientResolved flag) local to each store, or fold it into a small mixin/helper.
Context
Flagged as a non-blocking nit by the code reviewer on #819 (PR #824). Low priority, pure refactor, no behavior change — bundle with the next state-layer change or take standalone.
Summary
The
ensureClientForBucket()pattern — resolve the state bucket's region viaGetBucketLocation(cached) and rebuild a region-corrected S3 client reusing caller credentials without destroying the shared client — now exists in three near-identical copies:src/state/s3-state-backend.ts(original, PR feat(state): dynamic state-bucket region resolution + UnknownError normalization #60)src/state/lock-manager.ts(fix(state): LockManager does not resolve the state bucket's region — lock acquisition fails with 301 PermanentRedirect when the bucket region differs from the CLI's base region #803)src/state/export-index-store.ts(fix(state): exports index store does not resolve the bucket region — 301 PermanentRedirect on cross-region destroy (same class as #803) #819)This is pure duplication and a drift risk (a future fix to one copy can miss the others).
Proposed fix
Extract a shared helper, e.g. in
src/utils/aws-region-resolver.ts:that encapsulates the probe (resolve creds + region via the cached
resolveBucketRegion), the same-region short-circuit, and the credential-reusing rebuild (no.destroy()of the shared client). Each of the three call sites becomes a one-liner. Keep the per-call memoization (clientResolvedflag) local to each store, or fold it into a small mixin/helper.Context
Flagged as a non-blocking nit by the code reviewer on #819 (PR #824). Low priority, pure refactor, no behavior change — bundle with the next state-layer change or take standalone.