Skip to content

run_end_ceiling_ms falls back to the default for CLI-compatible 1e6 values #1359

Description

@sigley

Summary

run_end_ceiling_ms() says it reads CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS "as the CLI will see it" and notes that the CLI also reads spellings such as 1e6.

The current implementation uses int(raw), so 1e6 is treated as invalid and silently falls back to the SDK default of 600_000 ms instead of 1_000_000 ms.

Minimal reproduction

from claude_agent_sdk._internal.query import run_end_ceiling_ms

print(run_end_ceiling_ms({"CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS": "1e6"}))
# Actual:   600000
# Expected: 1000000

A smaller value shows the same mismatch in the other direction:

print(run_end_ceiling_ms({"CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS": "1e2"}))
# Actual:   600000
# Expected: 100

Why this matters

This value drives the Python SDK's own between-turn ceiling while it waits for the CLI's session_state_changed: idle after a result. If the SDK and CLI parse the same environment value differently, the SDK may close stdin earlier or later than the caller configured.

Expected behavior

Either:

  1. parse the same non-negative millisecond spellings that the CLI accepts, at least integral scientific notation such as 1e6; or
  2. narrow the docstring/tests so the SDK explicitly accepts only plain integer strings.

I think option 1 better matches the current docstring and the intent of reading the value as the CLI will see it.

Possible fix

Use a finite non-negative numeric parser that preserves the current behavior for:

  • default / unset values;
  • 0 meaning no limit;
  • whitespace around plain integers;
  • invalid strings falling back to the default;
  • negative values falling back to the default;
  • very large integer strings being honored and later clamped by the sleeper.

A regression test could add "1e6" -> 1_000_000 to TestRunEndCeilingFromEnv.test_parse.

Activity

  1. v0ropaev commented on Oct 7, 2026

    @v0ropaev

    Opened #1370 for this, option 1.

    Kept int() as the first attempt and only fall back to a float read when it fails, so the behaviours you listed as needing to survive do so by construction rather than by my re-deriving them: whitespace, 0, negatives, and the integer beyond float range, which keeps its exact value and is clamped later by the sleeper instead of turning into an infinity in the parser. inf and nan fall back to the default.

    Your six cases are in TestRunEndCeilingFromEnv.test_parse. Two fail on main, 1e6 and 1e2; the other four pass either way and are there as controls.

    One thing the issue leaves open and I had to pick: 1.5 now truncates to 1 rather than falling back to the default. Milliseconds are integral and truncation is what a float ceiling means downstream, but if you would rather a non-integral value stay a fallback, that is a one-line change and I will make it.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueGood for newcomers

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions