Repository navigation
refactor: return Results from tar pipe helpers in cache-store - #175
sunwrobert wants to merge 4 commits into
Conversation
|
I'll fix CI failures and address comments from users with write access that start with 'Devin'.
Original prompt from Devin Bot
|
EvidenceAPI-only change (package helper + warn logs), so no recording / before-after screenshots. Integration run against fake-gcs-server 1.54.0 with the real Same script against Unit proof: Visualization artifact |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
There was a problem hiding this comment.
✅ Approved. The tar create/extract helpers now return typed Results, and the cache callers translate failures into their existing warning and fallback outcomes. No confirmed blocking regression found.
Scope and evidence
Reviewed both changed files against actual base c6a05aa324948d4fd66b89c2c7e8b9a3065ee01a, at head 8258776e5a10047ecd890b1d17464ec334f282b1. Two independent correctness passes covered both files; additional passes examined error paths, security, reuse, coupling and callers. Checked process completion/error conversion, upload finalization after successful tar exit, kill-and-await cleanup, mkdir failure conversion, stream-opening handling and cache callers. Candidate defects were independently verified; none established a blocking regression.
This was a static review: no PR code, tests, installs or builds were run. The author's unit/integration results were not independently reproduced.
Worth a look: src/datasets/cache-store.ts:442-452 introduces an await between creating the writable and attaching pipeline listeners; a next-tick error would be unhandled, but whether the default GCS SDK emits errors that early could not be established because its implementation is absent from the checkout. This is an unproven timing concern, not a confirmed production defect.
Risk
| Dimension | Severity | Risk | Reasoning |
|---|---|---|---|
| Implementation risk | 🟩 | Low | Result branches preserve the checked tar-exit-before-upload-finalization ordering and kill/await cleanup (cache-store.ts:145-201); no confirmed regression. |
| Premise risk | 🟩 | Low | Existing Result utilities match the conversion; callers still warn and return false/void on represented failures (cache-store.ts:405-461). Early SDK stream timing remains unchecked. |
| Estimated impact | 🟨🟨 | Medium | A hypothetical stream-error regression could interrupt a benchmark worker, rather than only lose a cache hit; no such trigger was confirmed (cache-store.ts:442-452). |
| Risk factor | Severity | Risk | Reasoning |
|---|---|---|---|
| Reversibility | 🟩 | Low | No schema or archive-format change; prior helper/caller implementation can be restored. |
| Detectability | 🟩 | Low | Represented failures retain warning paths and keys (cache-store.ts:424-461); unit tests inspect failure Results and caller warnings. |
| Blast radius | 🟨🟨 | Medium | Changes affect the GCS checkout cache and its benchmark-worker callers, not the disk/JSON cache paths. A worker failure remains conditional on an unconfirmed error path. |
| Data integrity | 🟩 | Low | Creation still waits for successful tar exit before ending the upload (cache-store.ts:170-182); extraction format and arguments are unchanged. |
| Financial exposure | — | Unknown | Failed caches could repeat checkout work; workload frequency and resulting spend were not checked. |
| Security and privacy exposure | 🟩 | Low | No changed credentials, object-key policy or tar arguments; owner-only directory creation remains in place (cache-store.ts:206-219). |
| Propagation | 🟩 | Low | Failure Results are consumed by the two module-local cache callers (cache-store.ts:424,452), retaining their fallback outcomes. |
| Availability | 🟨🟨 | Medium | Conditional early writable errors could affect a worker; SDK timing is unverified, while represented tar/stream failures return through warning paths. |
| Recovery cost | 🟩 | Low | No new persisted format or migration requires reconciliation; source rollback restores previous behavior. |
| Time to correct | — | Unknown | Deployment and rollback cadence were not inspected. |
Verification and CI
Inspected tests covering tar exit, stream failure, missing executable, mkdir failure, upload success and caller warnings. These support the intended boundaries but do not establish the SDK timing noted above. At review time, all four check runs on this exact head succeeded (validate, CodeQL, and both Analyze jobs); the Devin Review commit status also succeeded. These are fetched CI results, not locally reproduced checks.
|
Re Perry's "worth a look" ( |
There was a problem hiding this comment.
✅ Approved. The tar-helper Result conversion retains the checked cache fallback and cleanup paths. The latest revision also removes the await between opening the upload stream and attaching pipeline listeners, addressing the prior timing note. No confirmed blocking regression found.
Scope and evidence
Re-reviewed the full actual-base diff, both src/datasets/cache-store.ts and src/datasets/cache-store.test.ts, at head a6e5095999c1f1efabe55733e3e2b7d24e69140e against base c6a05aa324948d4fd66b89c2c7e8b9a3065ee01a. Two independent correctness passes covered both files, with additional error-path, blast-radius, security, reuse and coupling passes. Checked Result branching, tar exit/error handling, pipeline finalization, kill-and-await cleanup, mkdir handling and the benchmark callers. Independently verified the candidate synchronous-spawn concerns; no meaningful supported trigger was established.
Static review only: no PR code, tests, installs or builds were run. Author-reported integration results were not independently reproduced. Third-party GCS internals were not available in the checkout.
Prior review
The previous Peary review approved with no blockers. Its non-blocking note is addressed: snapshotCheckout now uses synchronous Result.try (cache-store.ts:442-453), so opening the writable no longer adds an await before the helper attaches pipeline listeners. The earlier directory-creation failure conversion remains covered by mkdir_failed and caller tests. No new inline findings.
Risk
| Dimension | Severity | Risk | Reasoning |
|---|---|---|---|
| Implementation risk | 🟩 | Low | Checked tar/stream failures return Results, retain kill/await cleanup and preserve successful-exit-before-upload-finalization (cache-store.ts:145-225). |
| Premise risk | 🟩 | Low | Conversion uses existing Result conventions; the new synchronous write-opening wrapper preserves the previous listener-attachment timing (cache-store.ts:442-453). |
| Estimated impact | 🟩 | Low | Represented checkout-cache failures remain warn plus false/void, permitting existing fallback behavior (cache-store.ts:403-461); no confirmed escaping failure on supported paths. |
| Risk factor | Severity | Risk | Reasoning |
|---|---|---|---|
| Reversibility | 🟩 | Low | No new persisted format or migration; the previous helper implementation can be restored. |
| Detectability | 🟩 | Low | Existing keyed warnings remain on both caller failure paths (cache-store.ts:424-461); tests inspect returned kinds and warnings. |
| Blast radius | 🟩 | Low | Changed consumers are the GCS checkout hydrate/snapshot paths; disk and JSON-cache behavior is not refactored. |
| Data integrity | 🟩 | Low | Upload completion still follows successful tar exit (cache-store.ts:170-182); extraction flags and archive format are unchanged. |
| Financial exposure | — | Unknown | Repeated cache misses could add checkout work, but workload frequency and spend were not checked. |
| Security and privacy exposure | 🟩 | Low | No changes to credential handling, object-key policy or tar arguments; owner-only directory creation is retained (cache-store.ts:206-219). |
| Propagation | 🟩 | Low | Helpers' Results are consumed by the two module-local store methods (cache-store.ts:424,453) rather than exposed through a changed CacheStore interface. |
| Availability | 🟩 | Low | Checked failure paths continue to warn and return fallback values; synchronous upload opening no longer adds the earlier timing gap (cache-store.ts:442-461). |
| Recovery cost | 🟩 | Low | No format conversion, migration or new durable metadata requires reconciliation after a source rollback. |
| Time to correct | — | Unknown | Deployment and rollback timing were not inspected. |
Verification and CI
Inspected coverage for tar nonzero exit, missing executable, source/destination errors, mkdir failure, successful upload and caller warnings. At review time, all four current-head check runs succeeded: validate, CodeQL and both Analyze jobs. The current-head Devin Review commit status also succeeded. These CI results were fetched, not reproduced locally.
throw-to-result: src/datasets/cache-store.ts (vendored in openrouter-web as
packages/bench-harness/src/datasets/cache-store.ts)TL;DR
pipeTarGzToandextractTarGzFromreturnAsyncResult<void, TarGzError>instead of throwing;snapshotCheckout/tryHydrateCheckoutbranch on the Result with the samewLogfields and return values, and their generictry/catchis gone. The kill-and-await-exit cleanup is unchanged.What changed?
src/datasets/cache-store.tsNew
TarGzErrorunion next to the helpers (onekindper distinct failure, carrying the data the old message interpolated):waitForTarresolves to aResult(never rejects):close 0→succeed,close ≠ 0→tar_exited, childerrorevent →spawn_failed.streamStepwraps thenode:stream/promisescalls (pipeline,finished) with the package'stryPromiseResult+Result.mapError→stream_failed.extractTarGzFromwrapsmkdirOwnerOnly(dir)inResult.try→mkdir_failed(the old callercatchcovered this throw too; Devin Review caught that the first revision let it escape).abortTaris the shared cleanup (proc.kill(); await exit) for both helpers; when the process never started it returns thespawn_failedResult instead of the pipeline'sPremature close/ EPIPE.The two
throw new Error("tar … did not provide stdout/stdin")sites are deleted: thestdio: ["ignore", "pipe", "pipe"]tuple selects theChildProcessByStdio<null, Readable, Readable>overload ofspawn, soproc.stdout/proc.stdinare non-nullable and the checks were unreachable.describeTarGzErroris the exhaustiveswitch (error.kind)(ending insatisfies never) that renders the log text; the callers do not branch per kind because every kind maps to the same outcome (warn +false/void).Callers:
tryHydrateCheckoutwrapsclient.openObjectReadStreamwithtryPromiseResult(that call was inside the oldtrytoo), then branches onextractTarGzFrom's Result;snapshotCheckoutwraps the synchronousclient.openObjectWriteStreaminResult.try(no await beforepipeTarGzTo, so the pipeline attaches to the stream in the same tick as before) and branches onpipeTarGzTo's Result.src/datasets/cache-store.test.tsaborts the upload when tar exits nonzero:.rejects.toThrow("tar create exited")→assertFailure+kind === "tar_exited", same inputs.tar_exited,stream_failed,spawn_failed× create/extract,mkdir_failedfor extract), a success case forpipeTarGzTo, and caller tests for the warn log on a corrupt snapshot, a throwingopenObjectReadStream, an uncreatable target directory, a missing source dir, and a throwingopenObjectWriteStream.captureInfogeneralised tocaptureConsole("info" | "warn")inside theGcsCacheStoredescribe.Result library: this package already uses
effect/Result(src/internal/result.ts,src/internal/gcs.ts), so the conversion usesResult.fail/Result.succeed/Result.isFailure/Result.mapError/Result.tryfromeffect/Resultand the package'sAsyncResultalias, not@openrouter-monorepo/lib-result(which is not a dependency of this repo).remaining (this repo,
git grep -E "^\s*throw new Error\(" -- 'src/**/*.ts' ':!*.test.ts' ':!*.integration.test.ts' | grep -vE "// invariant:" | wc -l): 60 → 58remaining (openrouter-web inventory command over
packages/**+services/**, after the subtree pull lands this file): 1298 → 1296Callers (all in
src/datasets/cache-store.ts):makeGcsCacheStore().tryHydrateCheckout,makeGcsCacheStore().snapshotCheckout. No caller outside the module (git grep -n "pipeTarGzTo\|extractTarGzFrom"→ only this file and its test).// invariant:markers added: none.purely mechanical: no
errorfield ofGCS checkout hydrate failed/GCS checkout snapshot failedno longer carries theError:prefix thatString(error)added ("tar create exited 2: …"instead of"Error: tar create exited 2: …";"bucket unreachable"instead of"Error: bucket unreachable"). It now goes through the package'sunknownErrorToString, which is whatsrc/internal/gcs.tsalready logs. Field names, log messages, levels and return values are unchanged.taris not onPATH, the logged error is the spawn error (Executable not found in $PATH: "tar") instead of the downstream pipeline error (Premature close), becauseabortTarprefersspawn_failed.did not provide stdout/stdinthrows are removed rather than converted (see above).Prod impact: the bench worker's checkout cache (
BENCH_DATASET_CACHE_BACKEND=gcs). Same hit/miss/snapshot behaviour, same return values, same cleanup; only the two warn-log texts above differ. Worst case: a tar failure that previously surfaced as a rejected promise inside the caller'strynow surfaces as aResult.Failureon the same path, so the blast radius is the two log lines. No per-request allocation concern (not an inference path).Why?
Code-scan finding sfind-64accc3de1154543891546001134aef4 (Throw → Result burn-down): the helpers threw for missing stdio and rethrew pipeline failures, and their only callers caught generically and logged
String(error).How to test
Integration against a real GCS wire (fake-gcs-server 1.54.0 in Docker, real
@google-cloud/storagestreams, realtar):docker run -d --name fake-gcs -p 4443:4443 fsouza/fake-gcs-server:1.54.0 -scheme http -port 4443 -public-host localhost:4443 # the Node client sends JSON-API calls un-prefixed (/b/...), so a 10-line Bun proxy on :4444 rewrites /b/ → /storage/v1/b/ and forwards the rest OR_ENV=development LOG_LEVEL=1 STORAGE_EMULATOR_HOST=http://localhost:4444 GOOGLE_CLOUD_PROJECT=test bun run gcs-integration.tsExpected:
checkout cache snapshot written→ a 213-byteapplication/gzipobject;checkout cache hydrate … result: "hit"withtask.tomlandnested/data.txtextracted;result: "miss"→false; missing source dir →GCS checkout snapshot failed { key, error: "tar create exited 2: …" }and no object written.Show me
Proof matrix
Base = worktree at
origin/main(c6a05aa) with the head'ssrc/datasets/cache-store.test.tschecked out; head = a6e5095 (same test file as 8258776). Command for both:bun test src/datasets/cache-store.test.ts. Head: 32 pass / 0 fail. Base: 22 pass / 10 fail, every base failure an assertion or an unhandled rejection from the helper (no import/setup failure).pipeTarGzToreturnstar_exitedon nonzero exit and aborts the uploadaborts the upload when tar exits nonzero— base: rejectserror: tar create exited 2: …/missing: Cannot open; head: passGCS checkout snapshot failed … tar create exited 2, bucket unchanged (same on base,Error:prefix)snapshotCheckout, covered by the integration run)pipeTarGzToreturnsstream_failedwhen the destination rejects writespipeTarGzTo reports a stream failure when the destination rejects writes— base: rejectserror: disk full; head: passWritable)pipeTarGzToreturnsspawn_failedwhen tar is not on PATHpipeTarGzTo reports a spawn failure when tar is not on PATH— base: rejectserror: Premature close; head: passpipeTarGzTosucceeds and ends the destinationpipeTarGzTo succeeds and ends the destination for a real directory— base: fails (assertSuccesson aPromise<void>→result.successundefined); head: passcheckout cache snapshot written, 213-byteapplication/gzipobject (same on base)extractTarGzFromreturnstar_exitedon a corrupt archiveextractTarGzFrom reports tar_exited for a corrupt archive— base: rejectserror: tar extract exited 2:; head: passextractTarGzFromreturnsstream_failedwhen the source errorsextractTarGzFrom reports a stream failure when the source errors— base: rejectserror: connection reset; head: passextractTarGzFromreturnsspawn_failedwhen tar is not on PATHextractTarGzFrom reports a spawn failure when tar is not on PATH— base: rejectserror: Cannot pipe to a closed or destroyed stream; head: passextractTarGzFromreturnsmkdir_failedwhen the target cannot be createdextractTarGzFrom reports mkdir_failed when the target cannot be created— base: rejectsENOTDIR; head: passtryHydrateCheckout→false+ warn when the target directory cannot be created (unchanged input)tryHydrateCheckout returns false and warns when the target directory cannot be created— passes on base and head; failed on the first revision 0292bb9 (resolves.toBe(false)→ rejected)tryHydrateCheckout→false+GCS checkout hydrate failed { key, error }on a corrupt snapshot (unchanged input)tryHydrateCheckout returns false and warns when the snapshot is corrupt— passes on base and headtrue+ files extracted, miss →false(same on base)tryHydrateCheckout→false+ warn whenopenObjectReadStreamthrowstryHydrateCheckout returns false and warns when opening the object throws— base:toEqualfails, receivederror: "Error: bucket unreachable"; head: pass ("bucket unreachable")snapshotCheckout→ warn + no object on tar failure (unchanged input)snapshotCheckout warns and writes nothing when tar fails— passes on base and headsnapshotCheckout→ warn whenopenObjectWriteStreamthrowssnapshotCheckout warns when opening the write stream throws— base:toEqualfails, receivederror: "Error: bucket unreachable"; head: passGcsCacheStore/DiskCacheStore/resolveCacheStoretests — pass on base and headError:prefixThermo-nuclear fix loop
Three passes. Pass 1 findings, all fixed before the first push:
describeTarGzErrorwas exported with no external reader → module-private; the stdio null checks were dead under the typedspawnoverload → deleted instead of converted; thespawn_failedkind was unreachable from the pipeline path →abortTarconsults the settled exit Result;captureInfo/captureWarnduplicated →captureConsole(level);toHaveProperty("message")was a weak assertion →toContain("tar"). Pass 2 on 0292bb9: no findings; Devin Review then found the escapedmkdirOwnerOnlythrow →mkdir_failedkind + two tests (8258776). Pass 3 on 8258776: no findings. Perry's approval flagged thetryPromiseResult(async …)await beforepipeTarGzToas an unproven timing concern →Result.try(a6e5095), tests unchanged. Deslop-micro (fix mode) on the final diff: helper inventory (streamStep,abortTar,streamTarCreate,describeTarGzError,withoutTarOnPath,captureConsole) — no existing equivalent insrc/(rgforabort|kill.*exit|mapError.*stream), allkeep; name inventory (TarGzError,TarOperation,tar_exited,spawn_failed,stream_failed,mkdir_failed) —kinddiscriminants match the package's existing unions (verifier_log,judge_runs,rejected), allkeep; literal inventory —"create" | "extract"are the two tar invocations in this file, no shared vocabulary elsewhere,keep; no new comments. No cleanup commit was needed.Visualization
Artifact:
tar-helpers-result-2026-11-01.html(attached in the session;check-artifact.pypasses). Not committed. The artifact predates themkdir_failedkind; the three kinds it shows are unchanged.No
## Before / after: this change has no user-facing surface (package helper + warn logs), so the API-only evidence above replaces the recording and screenshots.Reviewer focus
abortTarpreferringspawn_failedover the pipeline error is the one place the returned kind depends on ordering;waitForTarsettles on the childerrorevent before the pipeline rejects in both Bun and Node (probe in the session), andabortTarawaitsexiteither way.streamTarCreatekeeps the three old steps in the same order (pipeline→await exit→dest.end(); finished(dest)) andpipeTarGzTostill destroysdestwith anErrorbefore cleanup, so a tar failure still aborts the GCS upload.Checklist
pipeTarGzTo/extractTarGzFromnow resolve to aResultinstead of rejecting; both are module-internal apart from the test (documented above)Link to Devin session: https://openrouter.devinenterprise.com/sessions/e506bddacbe34b6b9f3af4359f36e491
Open in Devin Desktop: https://openrouter.devinenterprise.com/desktop/session/e506bddacbe34b6b9f3af4359f36e491?variant=devin
Requested by: @sunwrobert