Studio: price a partial GGUF by what is left to fetch by shimmyshimmer · Pull Request #8989 · unslothai/unsloth · GitHub
Skip to content

Studio: price a partial GGUF by what is left to fetch - #8989

Merged
danielhanchen merged 24 commits into
mainfrom
studio/partial-remaining-bytes
Aug 19, 2026
Merged

Studio: price a partial GGUF by what is left to fetch#8989
danielhanchen merged 24 commits into
mainfrom
studio/partial-remaining-bytes

Conversation

@shimmyshimmer

Copy link
Copy Markdown
Member

Follow-up to #8927, and to the reports that the Model hub re-downloads models people already have.

What people are seeing

Two different things get reported as one bug, and only one of them is real.

A model that finished downloading is never fetched again. huggingface_hub checks the blob path before it makes any request and symlinks the cached blob into the snapshot when the etag matches, and nothing in the hub download path passes force_download.

An interrupted file is a different story. Since 1.18 the writer is a process-unique <etag>.<uuid>.incomplete opened "wb" and unlinked in a finally (huggingface/huggingface_hub#4228), so there is no ranged resume and the partial is not even kept. We pin huggingface-hub>=1.23.0 on Python 3.10+, so on a current install nothing resumes inside a file. Reuse is whole-file only: snapshot_download skips shards that are already materialized.

So the honest cost of continuing a partial is "every file that did not finish", and the card was not showing that. It printed the variant total, which reads as "all of this is about to download again" no matter how much was already there.

The change

The variants endpoint now reports download_remaining_bytes on a partial variant: the plan total minus existing_blob_bytes, which already applies exactly the right rule (finalized blobs count, an unresumable partial does not, a blob a live peer holds the lock on does).

The card shows that number with a "left" suffix on partial rows only:

Case Before After
56 GB sharded variant, 40 GB fetched 56 GB 16 GB left
18 GB one-file quant, 17 GB fetched 18 GB 18 GB left
Not partial 18 GB 18 GB

The second row is not a bug. A single-file quant has no other file to keep, so continuing it really does transfer all 18 GB, and the number now says so instead of leaving people to find out at the end. An unmeasured partial falls back to the total rather than guessing lower.

Cost is bounded: the scan runs only for variants already known to be partial, which is normally at most one per repo.

Tests

  • studio/backend/tests/test_partial_remaining_bytes.py, new: a finished shard is subtracted, an unresumable partial is not, a one-file quant prices whole, an unresolvable plan reports null rather than guessing.
  • studio/frontend/tests/gguf-variant-transfer-size.test.ts, new: covers the label including the "left" suffix and the fallback.

npm run typecheck, npm run build, the new suites and test_gguf_variant_rows.py pass. Pre-existing on a clean checkout, not from this branch: tests/delete-chat-files-preference.test.ts (imports chat/utils/pasted-text, absent from the tree), test_the_sweep_will_not_cross_a_case_variant_directory (needs a case-sensitive filesystem), and a handful of test_gguf_variant_rows.py cases that only fail when that module shares a process with other suites.

I have not driven this in a live UI: reproducing it means interrupting a real multi-shard download.

Not in this PR

Independent of #8982, which fixes the tooltip and the button label on the same rows. Either can land first.

The deeper fix is resume itself. We could range-fetch into a stable .incomplete under our own control instead of relying on the hub's writer, which would turn that 18 GB back into 1 GB. That is a real change to the download path and wants its own discussion, so I have kept it out of here.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

shimmyshimmer and others added 5 commits August 17, 2026 02:49
The size beside a partial variant was the variant total, so continuing a
sharded download that was already 40 GB in still read "56 GB" and looked like
the whole model coming down again. That is the reading behind the reports of
the hub re-downloading models people already have.

The variants endpoint now reports download_remaining_bytes for a partial: the
plan total minus the bytes on disk a transfer can actually reuse. Reuse is
per file, so a finished shard counts and an unresumable partial does not, and
the card shows "16 GB left" instead of "56 GB".

A one-file quant therefore still reads back its full size, because that is
what continuing it costs: huggingface_hub 1.18+ refetches an interrupted file
from zero, and a single-file quant has no other file to keep.
The local and offline listings return before the hub-plan path, so their partial
rows carried no remaining figure and fell back to the full total. The on-device
card asks for exactly those (preferLocalCache), which is where a partial is most
likely to be looked at.

The worker writes its manifest before fetching anything, so those rows can be
priced from the file list that produced them. Capped at the row's own total,
since a manifest counts companions the row's size may not.

The on-device card also merges local rows over remote ones, so it now carries
the remaining figure through instead of dropping it.
A local listing sizes a variant by summing the shards it can see, so an early
interruption makes that total smaller than the transfer: three 2 GB shards with
one cached advertises 2 GB, and capping the remainder by it reported "2 GB left"
when 4 GB had to be fetched. Under-reporting is the one direction this figure
must never be wrong in.

The cap was there to stop a companion-inclusive remainder reading larger than
the row's own size, but no surface shows the two together: a partial row's size
chip IS the remainder. So the cap is dropped and the manifest total stands.
@shimmyshimmer
shimmyshimmer force-pushed the studio/partial-remaining-bytes branch from 2c7cb96 to 9cdc090 Compare August 17, 2026 09:50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9cdc090121

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

except Exception as e:
logger.warning(f"Remaining-bytes lookup failed for {repo_id}: {e}")
return None
return max(0, requirement.download_size_bytes - have)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Deduplicate blob sizes before computing the remainder

When two expected target filenames reference the same content hash, requirement.download_size_bytes counts that blob once per filename while existing_blob_bytes() counts it only once per hash. Such aliases therefore report one blob still left even when the shared blob is already cached, and report twice the actual transfer when it is absent. The download worker's _preflight_disk_space() explicitly deduplicates expected sizes by hash for this case; compute the total here the same way before subtracting have.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The asymmetry is real (download_size_bytes sums per file, existing_blob_bytes credits per hash) but the premise is not reachable. I probed 750 real GGUF repos with model_info(files_metadata=True): zero plans contain a duplicate hash. 136 of 750 do ship duplicate LFS oids, but every one is cross-variant, so they land in different plans; the single same-variant case (unsloth/QwQ-32B-GGUF) is already dropped by _one_shard_family, and a dflash- file cannot self-pair because _DRAFTER_KINDS excludes it from main. Not fixing an unreachable case. Separately, the remainder cannot go negative (max(0, ...)) or exceed the planned total.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@danielhanchen

Copy link
Copy Markdown
Member

Review summary

Verdict: useful, merge it - with the download_registry.py fix that went in as 315e77b, because this PR is what puts that function's output on screen.

Before / after

Before, a partial row printed the variant's full size beside its Resume button: formatBytes(ggufVariantDownloadSizeBytes(variant)). Resuming a 56 GB sharded download that was already 40 GB in still read "56 GB", which reads as "the whole model is coming down again". After, the backend measures what a resume must still fetch (plan total minus reusable blobs) and returns it as download_remaining_bytes, so the card prints 16 GB left. A one-file quant honestly still reads its full size, because an unresumable partial really is refetched whole.

Real issue, and correctly scoped. The reuse model it encodes - whole files are kept, a nonce-suffixed .incomplete is never reopened - matches upstream: huggingface_hub v1.18.0 shipped PR #4306, which writes f"{stem}.{uuid4().hex[:8]}.incomplete" opened "wb", with release notes stating that resuming a previously failed partial download is no longer possible. Verified by installing real hub 1.27.0 and SIGKILLing a download, which left a real nonce partial that Studio parsed and classified correctly.

Does it break anything

No. The new field is additive and optional, and no decision path consumes the remainder: fit classification still reads variant.size_bytes, sorting still reads size_bytes, download-manager expectedBytes still reads the full download_size_bytes, and the ETA bar computes its own remainder from live job bytes. The only disk-space preflight is backend-side and derives its own number, so the remainder can never under-state required disk space.

Old frontend against new backend: there is no zod anywhere in the frontend and listGgufVariants ends at a raw response.json() with a TypeScript-only cast, so an unknown key is ignored. New frontend against old backend: the field is optional, absent becomes undefined, and the card falls back to the full total - tested explicitly over [null, undefined, -1]. Old persisted state has no exposure: variants live only in an in-process LRU, and the only persisted hub store partializes ManagedDownload jobs. Old manifests: 33 hand-written legacy and hostile manifests (v1 without repo_type/hub_cache, versions 0/3/99/"2"/true/null, string/negative/float sizes, absolute paths, ../ traversal, truncated JSON, a 200k-deep recursion bomb, another variant's manifest) all degrade to None - unpriced, never a wrong number, never a crash.

Two bugs found and fixed (315e77b)

Both live in existing_blob_bytes, which pre-dates this PR - but this PR is what puts its output on screen as the "N left" label, and both make it render literally "0 B left" next to a Resume button while gigabytes are still missing.

1. A sparse partial was credited by its logical size. hf_transfer's parallel Range writer leaves a sparse .incomplete whose st_size runs ahead of the bytes actually written - the module's own docstring says so, and the sibling helper hf_cache_state.blob_bytes_present exists for exactly this and is already used by snapshot_progress.py. Measured on a real interrupted download: 341 MB on disk reported as 1.32 GB, giving "2.10 GB left" when at least 3.08 GB was missing; four seconds later, 2.58 GB on disk with st_size at the full 3.42 GB, giving a remainder of 0.

2. One blob was counted once per repo directory. present was rebuilt inside the iter_active_repo_cache_dirs loop with a total += per directory. The Hub resolves repo ids case-insensitively while repo_folder_name keeps the caller's casing, so a case-sensitive filesystem holds models--Org--Model beside models--org--model. Reproduced with two real hf_hub_download calls differing only in casing: a 1.6 GB variant missing a 770 MB shard reported 0 left.

Both fixes are strictly more conservative, so the other two consumers only get safer, and blob_bytes_present is already Windows-aware via its GetCompressedFileSizeW fallback. Both new tests fail on pristine head and pass with the fix; 13 passed, ruff clean.

On the dedup item

I did not take the staged gguf_plan.py hardening. The asymmetry is real (download_size_bytes sums per file, existing_blob_bytes credits per hash), but I probed 750 real GGUF repos with model_info(files_metadata=True) and found zero plans containing a duplicate hash. 136 of 750 do ship duplicate LFS oids, but every one is cross-variant so they land in different plans; the single same-variant case is already dropped by _one_shard_family. Fixing an unreachable case is not worth the diff.

Simulation

151 assertions across 6 offline groups plus 7 network groups that download real GGUF shards and SIGKILL them mid-flight - real cache trees on disk, not mocks. 147 pass; the 4 failures produced the two bugs above.

Cross-platform: the accounting reads blobs/ only, so the Windows layout (copies rather than symlinks) is unaffected - simulated with real copies. Nine path spellings pass: spaces, CJK and Cyrillic, emoji, WSL /mnt/c/Users/Dev User/..., drive-letter, backslash-bearing names, long paths. Browsers: the only new syntax is ?? and ?. (ES2020 - Chrome 80, Firefox 72, Safari 13.1), and formatBytes uses toFixed rather than Intl.NumberFormat; Vite's target resolves to chrome111/firefox114/safari16.4, so every construct is far below the floor.

Two things for the author

  • The local-on-device-card.tsx hunk is inert. It merges download_remaining_bytes, but that component never reads it - both render sites are formatBytes(...size_bytes) and it does not import ggufVariantTransferLabel. So the card that asks with preferLocalCache: true, the one the backend test's docstring names as the motivation, is the card that never displays the result. Either drop the hunk or wire those two lines up (the latter is a real UI change needing its own screenshot).
  • 0 B left is still reachable on a correctly computed remainder, via a repo with several mmproj precisions where an interrupted download left a non-preferred one incomplete: mmproj_hashes is repo-wide while download_size_bytes covers only the preferred one, so a fully-cached variant is marked partial with a true remainder of 0. The mis-classification pre-dates the PR; the PR makes it visible.

Smaller: a live download's chip does not count down (it flips 56 GB to 56 GB left on click and holds a resumed row's pre-resume remainder) - both the safe larger number, with the progress bar beside it authoritative.

CI

The single red check is an unrelated flake: tests/kaggle/test_launch_cleanup.py::test_the_exit_status_survives_a_release_that_fails, a subprocess SIGTERM race in the Kaggle launcher, with 8064 passing alongside it. This PR touches only studio/ and changes no kaggle code, no signal handling and no test collection; it runs 10/10 green locally at head. A re-run should clear it.

@danielhanchen

Copy link
Copy Markdown
Member

UI evidence

Partial GGUF resume size, before and after

Two isolated Studio installs, each built from its own checkout with install.sh --local, both pointed at one isolated HF cache.

  • BEFORE: 4d1f9c519e7ae3dbf41cf35dc5d7aa5c23583ec5 (merge base of this PR, not main)
  • AFTER: e281a268dd09bff5a61639be923c3d9552005c10 (head)

Same scene on both sides: purge unsloth/gemma-3-4b-it-GGUF from the cache, download Q4_K_M through Studio's own worker over HTTP, then delete mmproj-F16.gguf (851251328 bytes, blob and snapshot symlink) so the manifest still expects a file that is no longer there. The variant is then genuinely partial with one known file missing, identically on both sides. A mid-flight cancel would have stopped the two sides at different byte counts, which is why the remainder is made deterministic instead.

What moved:

  • resume affordance on the download card: Q4_K_M Partial GGUF 3.3 GB Continue to Q4_K_M Partial GGUF 851 MB left Continue
  • the same quant's row in the picker: size chip 3.3 GB to 851 MB left
  • /api/hub/gguf-variants for that row: download_remaining_bytes absent to 851251328, which is exactly the file that was removed

What did not move, and is the control that makes the pair readable: every other row in the same list is byte for byte the same (BF16 8.6 GB, UD-Q8_K_XL 6.0 GB, Q8_0 5.0 GB, UD-Q6_K_XL 4.4 GB, Q6_K 4.0 GB, UD-Q5_K_XL 3.7 GB, Q5_K_M 3.7 GB, Q5_K_S 3.6 GB), and the partial row keeps size_bytes 2489894016 and download_size_bytes 3341145344 on both sides. Only the pricing of the partial changed.

Not covered by this pair:

  • the backend half of the PR. A blob shared across case-variant repo dirs being credited once, and a sparse .incomplete measured by blocks present rather than st_size, both need cache states this host cannot produce on demand. The unit tests carry those.
  • the sharded case the description is written around. A quant large enough to be split is tens of GB per shard, so the remainder here comes from a companion file rather than a finished shard.
  • the local and offline listings (variant_remaining_bytes_from_state). This pair is the online Hub path only.
  • one thing observed while measuring rather than shown in the shot: the legacy /api/models/gguf-variants copy of the same row omits download_remaining_bytes on both sides, since it serialises through its own schema in models/models.py. Nothing that calls ggufVariantTransferLabel reads that route, so the card is unaffected, but a future caller of it would still price a partial at the full total.

Useful: the number on the resume button now says what the resume costs, and the pair shows it changing only for the partial row.

The live overlay carried only the expected size, so a row that says
N left kept whatever the one-time variant fetch had measured, or the
full total for a download started after it. A download 90 percent
through read as though nothing had moved. The running job already
carries its own progress, so derive the remainder from it and keep the
fetched figure for every other row.
chatgpt-codex-connector[bot]

This comment was marked as resolved.

snapshot_progress nets completed_baseline_bytes out of expected_bytes
and downloaded_bytes alike, so the job's two counters are consistent
with each other and not with the catalog totals. Subtracting the job's
transfer from the larger of the two scopes added that baseline straight
back: 1 GB reused and 1 GB fetched of a 5 GB plan read 4 GB left rather
than 3 GB. The catalog total still drives the size the row reports.
chatgpt-codex-connector[bot]

This comment was marked as resolved.

danielhanchen and others added 2 commits August 18, 2026 16:21
A row can name a snapshot in a previous, legacy or default HF cache
root, and the request already scopes partial detection and the manifest
read to it, but the blob scan always read the active root. Wrong in both
directions: shards already in the pinned root earned no credit, and a
copy of the same blob in the active root earned credit a resume into the
pinned root cannot use, which reported less left than there is. The
docstring called counting the active root only the safe direction to be
wrong in, and it is for the first half, but not for the second.

The fixture stub also had to start honouring an explicit root; ignoring
it would have answered the active root for a pinned row and hidden the
behaviour the new tests are about.
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 18, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 18, 2026
@danielhanchen

Copy link
Copy Markdown
Member

@codex review

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 18, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 18, 2026
@danielhanchen

Copy link
Copy Markdown
Member

@codex review

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 19, 2026
@unslothai unslothai deleted a comment from shimmyshimmer Aug 19, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 19, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 19, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 19, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 19, 2026
chatgpt-codex-connector[bot]

This comment was marked as resolved.

An XET run that falls back to HTTP re-claims in the same generation with a completed_baseline_bytes recomputed from disk, so the baseline now covers every blob the XET attempt finalized and the new run reports completed_bytes 0 against a shrunken total. resolveProgressUpdate holds the previous reading through that zero, so the card kept the dead run's finalized bytes while its total moved to the retry's scope, and taking the max of the two counters subtracted 3 GB from a 0.5 GB remainder: the row read 0 B left with the transfer barely started. snapshot_progress builds downloaded_bytes as completed plus in-flight and nets the same baseline out of both, so a single reading never has completed above downloaded and the max could only ever fire on a held figure.
chatgpt-codex-connector[bot]

This comment was marked as resolved.

resolveProgressUpdate holds the previous downloadedBytes through a poll
that reported zero, and the GGUF row forwarded that held figure as the
current transfer. An XET run that falls back to HTTP re-claims in the
same generation with completed_baseline_bytes recomputed from the
finalized blobs on disk, and snapshot_progress nets that baseline out of
downloaded_bytes, completed_bytes and the total alike, so the retry's
first reading is a legitimate 0 against a shrunken total. Subtracting
the held 3 GB from the 0.5 GB that remained read "0 B left" until the
retry moved its first byte.

Carry the distinction out of resolveProgressUpdate as measuredTransfer
and skip the progress-derived remainder on a held reading, falling back
to the backend's own download_remaining_bytes the way the terminal row
already does.
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 19, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 19, 2026
chatgpt-codex-connector[bot]

This comment was marked as resolved.

measuredTransfer marks a reading the poll did not measure, so the remainder
is not derived from a stale downloadedBytes. It was not persisted, so a
reload restored the stale bytes beside the new run's shrunken expectedBytes
with the flag reading undefined, which the selector treats as measured. That
is the '0 B left' the guard exists to stop, and on app start it lasts until
the first poll returns rather than a single tick.

Persist and restore the flag. Absent stays absent, so a record written before
this field still means never polled rather than held.
chatgpt-codex-connector[bot]

This comment was marked as resolved.

A restored fallback job is adopted by the next running-status probe, which
seeds the persisted byte counters but rebuilt the job without the marker.
That restored it as undefined, which reads as measured, so the remainder was
derived from the dead run's bytes again and the row read 0 B left.

The marker is a statement about those counters, so it travels with them:
seededMeasuredTransfer joins carriesOverSeed in adopt-rules, gated on the
same condition. Not carrying the seed zeroes the counters, so there is no
held figure left and undefined stays the honest answer.
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 19, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 19, 2026
chatgpt-codex-connector[bot]

This comment was marked as resolved.

blob_bytes_present read a missing st_blocks and a present zero as the same
thing, so a partial set to its final length before its first chunk landed
fell through to st_size and was credited whole -- the row read 0 B left on a
download that had transferred nothing. Confirm the emptiness with SEEK_DATA
rather than inferring it, so a mount that never populates st_blocks keeps the
size fallback.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01bc342eb8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +67 to +69
// Undefined is a job that has not polled yet, which has no held
// figure to forward; only an explicit false is a held reading.
measuredTransfer: job.measuredTransfer !== false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle legacy persisted counters as unmeasured

When an active download is restored from persistence written by the previous frontend, measuredTransfer is always undefined even if that job had already polled; treating every undefined value as measured therefore misclassifies a held nonzero counter. In particular, a reload or upgrade during an XET-to-HTTP reclaim can restore the old run's downloaded bytes beside the retry's smaller total and derive 0 B left, recreating the issue this marker is intended to prevent until a positive poll arrives. Treat an absent marker conservatively when a legacy record already carries nonzero counters, or migrate/version the persisted state.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, and this overturns a call I made two rounds ago. I wrote that comment as "absent stays absent, so a record written before this field still means never polled", on the reasoning that an older job has no held figure to distrust. That reasoning was wrong: an older job can absolutely be holding one, it just has no way to say so.

Fixed in e95d89a, taking the migrate/version route rather than the blanket one. The two remedies are not equivalent here. Reading every absent marker beside nonzero counters as held would also catch current records, where an absence is a real statement ("this job has not polled yet"), and would suppress the tightened remainder for a poll on jobs that never had a problem. So PERSIST_VERSION goes to 2 and migrate passes a legacy flag down to sanitizePersistedJob; only a record restored from below version 2 with downloadedBytes > 0 is read as held. Anything written from this build onward keeps absent meaning never polled.

Downgrade is unaffected: an older build reading a version-2 record runs its own migrate and ignores the field it does not know about.

Tests in studio/frontend/tests/download-legacy-measured-migration.test.ts: a legacy record carrying counters restores as held, a legacy record with nothing counted stays unknown. Mutation-checked in three directions, each of which fails a test: dropping the new branch, forcing the version gate false, and reverting PERSIST_VERSION to 1. Worth noting that a fourth mutation (passing legacy = true unconditionally) fails nothing, because migrate only runs on a version mismatch in the first place, so there is no current-version path through it to catch.

tests/download-transport-persistence.test.ts was already writing version 1, so it now exercises the migration incidentally and still restores transports intact. 77 tests pass across the nine download suites, typecheck clean.

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 19, 2026
@danielhanchen

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 01bc342eb8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

A record written before measuredTransfer existed cannot say whether its byte
counters were measured, so an absent marker there is not the never-polled it
means in a current record. An upgrade landing mid-reclaim restored the dead
run's held bytes with the guard reading measured, which is the 0 B left the
marker exists to stop. Version the persisted state so the migration can tell
the two absences apart, and read only the pre-marker one conservatively.
@danielhanchen

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: e95d89af1d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danielhanchen
danielhanchen merged commit 77bc10c into main Aug 19, 2026
4 of 5 checks passed
@danielhanchen
danielhanchen deleted the studio/partial-remaining-bytes branch August 19, 2026 14:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ed0b21985

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the backend-adjusted total in the live remainder

When the backend subtracts a reusable companion baseline, live.expectedBytes is not guaranteed to retain that adjusted total: the effect in gguf-download-card.tsx lines 694-705 raises the job back to the catalog's full download_size_bytes after every poll. A 5 GB plan with a reused 1 GB companion and 1 GB transferred therefore reaches this calculation as 5 GB - 1 GB and displays 4 GB left rather than the actual 3 GB. Fresh evidence beyond the earlier zero-progress case is that this happens after a positive measured poll, because the existing effect repeatedly overwrites the backend-owned total before this subtraction.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants