Repair the prompt-queue contract test against the queueing refactor by danielhanchen · Pull Request #9026 · unslothai/unsloth · GitHub
Skip to content

Repair the prompt-queue contract test against the queueing refactor - #9026

Merged
danielhanchen merged 2 commits into
mainfrom
fix-prompt-queue-contract
Aug 17, 2026
Merged

Repair the prompt-queue contract test against the queueing refactor#9026
danielhanchen merged 2 commits into
mainfrom
fix-prompt-queue-contract

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

Repo tests (CPU) is red on main. test_composer_only_queues_behind_the_current_chat greps thread.tsx for code that #8952 moved or replaced, so it fails on any branch regardless of what that branch changes.

It is currently the only failing check on #8935, #8964, #8980 and #8983. Reproducible on a clean checkout of main with no PR content:

E   AssertionError: assert 'startHydratedPromptQueue(' in '\n    (event: {\n      preventDefault: () => void;\n ...
FAILED tests/studio/test_multi_chat_prompt_queue_contract.py::test_composer_only_queues_behind_the_current_chat

The behaviour is intact

Three assertions drifted. In two of the three the code that replaced them is stronger than what the test still asserted, so they are repointed rather than dropped.

Queueing moved behind a helper. handleSubmit no longer calls startHydratedPromptQueue inline; #8952 extracted queueComposerText so the Cmd/Ctrl+Enter path could share it. handleSubmit now calls queueComposerText(liveThreadIsRunning || livePreStreamRunActive), and the queueing, the composer read and the untrimmed guard all live in that helper. The test now asserts the delegation in one and the queueing in the other, which keeps this a contract on behaviour instead of on where the code happens to sit.

Presence became identity. promptQueueStartPendingRef.current.has(reservationKey) became promptQueueStartPendingRef.current.get(reservationKey) === reservation. A reservation can be replaced between the queue start and the callback, and acting on the successor would dispatch the wrong prompt. Presence alone never caught that.

Temporary is captured, not read live. temporary: useChatRuntimeStore.getState().incognito became temporary: incognitoAtQueueStart, captured when the queue starts rather than read when it dispatches. A chat toggled out of temporary mid-queue must not have its already-queued prompts persisted, which reading the store at dispatch time would do.

Testing

Each rewritten assertion was checked against a deliberately broken tree rather than assumed to discriminate:

mutation to thread.tsx result
handleSubmit no longer delegates to queueComposerText 1 failed
reservation checked by presence instead of identity 1 failed
temporary read live instead of captured at queue start 2 failed
queueComposerText stops queueing 1 failed
none (clean) 11 passed

tests/studio whole directory: 3,887 passed, 4 skipped.

Repo tests (CPU) is red on main. test_composer_only_queues_behind_the
_current_chat greps thread.tsx for code that #8952 moved or replaced, so
it fails on any branch regardless of what that branch changes. It is
currently red on #8935, #8964, #8980 and #8983 for this reason alone.

The behaviour it guards is intact, and in two of the three cases the
code that replaced it is stronger than what the test still asserted, so
the assertions are repointed rather than dropped:

- Queueing moved out of handleSubmit into an extracted queueComposerText,
  so the Cmd/Ctrl+Enter path could share it. Assert the delegation in
  handleSubmit and the queueing inside queueComposerText, which keeps
  this a contract on behaviour rather than on where the code sits.

- promptQueueStartPendingRef.current.has(reservationKey) became a
  .get(reservationKey) === identity comparison. A reservation can be
  replaced between the start and the callback, and acting on the
  successor would dispatch the wrong prompt; presence alone never
  caught that.

- temporary: useChatRuntimeStore.getState().incognito became
  temporary: incognitoAtQueueStart, captured when the queue starts
  instead of read live at dispatch. A chat toggled out of temporary
  mid-queue must not have its queued prompts persisted.

Each rewritten assertion was checked against a deliberately broken tree
rather than assumed to discriminate. Removing the delegation, swapping
the identity check back to presence, reading temporary live again, and
stopping queueComposerText from queueing each fail the file.

tests/studio 11 passed for this file, whole directory green.

@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: 941c03c703

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

# Identity, not mere presence: a reservation can be replaced between the
# start and the callback, and acting on the successor would dispatch the
# wrong prompt. `.has` only asked whether the key was occupied.
assert "promptQueueStartPendingRef.current.get(reservationKey) ===" in THREAD

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 Scope the identity assertion to the dispatch guard

If the dispatch condition at startPromptQueue(...) regresses to promptQueueStartPendingRef.current.has(reservationKey), this test still passes because the later abort and cleanup branches retain matching get(reservationKey) === reservation expressions. That allows a superseded reservation to dispatch the replacement's prompt—the exact regression this new assertion is intended to catch—so assert the identity comparison within the dispatch condition or require all relevant comparisons explicitly.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Correct. The assertion searched thread.tsx as a whole, and the abort and cleanup branches beside the dispatch carry the same identity comparison, so the dispatch guard could regress to .has(reservationKey) on its own and the test would pass on its neighbours, which is precisely the regression it exists to catch. It now slices the if condition guarding the startPromptQueue call and asserts the comparison there, with .has excluded from that guard, and still requires all three comparisons by count since the other two are load-bearing as well: abort without it reports the successor start as this one failing, cleanup without it deletes the successor entry. 11 passed, and rewriting the dispatch guard to .has now fails the test where it passed before.

Reported on this PR and correct. The assertion searched thread.tsx for
promptQueueStartPendingRef.current.get(reservationKey) === anywhere, and the
abort and cleanup branches beside the dispatch carry the same comparison. The
dispatch guard could regress to .has(reservationKey) on its own, which is exactly
the bug this assertion exists to catch, and the test would still pass on its
neighbours.

It now slices the if condition that guards the startPromptQueue call and asserts
the identity comparison there, with .has excluded from that guard. All three
comparisons are still required by count, because the other two are load-bearing
too: abort without it reports the successor's start as this one's failure, and
cleanup without it deletes the successor's entry.

11 passed. Rewriting the dispatch guard to .has fails it; it passed before.
@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

@danielhanchen
danielhanchen merged commit 408d88e into main Aug 17, 2026
21 of 22 checks passed
@danielhanchen
danielhanchen deleted the fix-prompt-queue-contract branch August 17, 2026 03:21
danielhanchen added a commit that referenced this pull request Aug 17, 2026
Following up my own reasoning for accepting #8943's reversal. The reseed is
correct because the same flag gates the remembered lookup: it does not blank the
slot control, it re-reads THIS model's saved config through
resolveResidentInitialConfig. Without that the reversal would take the user's
saved slot count away for real, so it is asserted rather than left implicit.

Also confirmed the attribution exactly: #8943's direct parent is #9026, which is
clean, so there is no intervening commit and one introducer.

  184 passed, and dropping slotsModelChanged from the remembered gate fails it
danielhanchen added a commit that referenced this pull request Aug 17, 2026
* Repair the contract tests #8943 left behind, and correct one that pinned the old behaviour

Repo tests (CPU) has been failing on main since #8943 landed: 54 tests across
tests/studio/test_new_chat_context_recount.py and
tests/studio/test_model_picker_contracts.py. #9026, immediately before it, is
clean. Nothing in the batch of test fixes merged after it caused this.

Three separate problems, and the third is a behaviour change, not a stale anchor.

1. test_new_chat_context_recount.py sliced loadModel's already-resident branch by
   a comment #8943 rewrote, so 52 tests died at the slice. That branch is also no
   longer a short early return: it now IS the residency decision, reconciling GPU
   pools, speculative type, managed llama flags and per-model config through 17
   imported collaborators. Re-anchoring to the top of it would have meant stubbing
   all 17, and a replay under 17 stubs asserts against a construction rather than
   the product.

   The slice is now the adoption TAIL, which is what this file is about: what
   happens after the model is judged resident. The judgement itself is stubbed as
   `adoptable` and is covered on its own by the resident-model-match and
   resident-config-match suites #8943 added. Six stubs, each derived exactly the
   way the source derives it, and the two enclosing conditions restated so the
   entry conditions are visible rather than implied.

   setParams is stubbed to the merge the tail depends on rather than sliced: the
   real reducer reaches preset policy, per-turn counters and loaded-context
   bookkeeping, none of which this file measures.

2. test_external_readoption_drops_a_pin_taken_for_another_model sliced on a
   condition that no longer exists. The contract survives in a better form: the
   branch used to clear the pin to null, and now adopts this pick's own pin by the
   rule a completed load writes it, which drops a stale pin the same way and keeps
   a pinned cached row loadable. Rewritten against that, ordering requirement
   unchanged.

3. test_hydration_keeps_the_slot_control_when_readopting_the_running_model was
   asserting behaviour #8943 deliberately reversed, and #8943 is right. Its commit
   says why: the adopt path rolls the outgoing model's config back into the store
   before it hydrates, so the slot and batch controls sitting there belong to the
   model the tab just left, and suppressing the reseed left a resident model
   running 4 slots showing the outgoing count for the next Apply to save over it.
   The test now pins the new contract, asserts nothing reintroduces a same-model
   exemption, and additionally pins the ordering that makes the reseed necessary:
   the rollback must precede the hydration.

  tests/studio: 3896 passed, 4 skipped
  reinstating the readoptingSameModel guard, or moving the rollback after the
  hydration, fails the rewritten test

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Pin why the reseed is safe, not only that it happens

Following up my own reasoning for accepting #8943's reversal. The reseed is
correct because the same flag gates the remembered lookup: it does not blank the
slot control, it re-reads THIS model's saved config through
resolveResidentInitialConfig. Without that the reversal would take the user's
saved slot count away for real, so it is asserted rather than left implicit.

Also confirmed the attribution exactly: #8943's direct parent is #9026, which is
clean, so there is no intervening commit and one introducer.

  184 passed, and dropping slotsModelChanged from the remembered gate fails it

* Tighten the comments added by this PR

Comments and docstrings only. The AST gate reports a difference because the
harness is a Python string holding TypeScript, so its // comments are inside a
string literal; every changed line is a comment line in that string or a
docstring, checked line by line, and the tests still pass.

  237 passed

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant