Skip to content

fix(chat): enforce ConcurrencyConfig.max_concurrent + release 0.4.26.1#60

Merged
patrick-chinchill merged 7 commits into
mainfrom
fix/max-concurrent-enforcement
Apr 23, 2026
Merged

fix(chat): enforce ConcurrencyConfig.max_concurrent + release 0.4.26.1#60
patrick-chinchill merged 7 commits into
mainfrom
fix/max-concurrent-enforcement

Conversation

@patrick-chinchill
Copy link
Copy Markdown
Collaborator

@patrick-chinchill patrick-chinchill commented Apr 23, 2026

Fixes #51. Release prep for 0.4.26.1.

What

ConcurrencyConfig.max_concurrent was accepted at Chat.__init__ and never read anywhere (3 writes, 0 reads). Upstream TS has the same gap — the field is documented as "Default: Infinity" but never enforced. Consumers setting concurrency=ConcurrencyConfig(strategy="concurrent", max_concurrent=5) got unlimited concurrency silently.

This PR:

  • Adds an asyncio.Semaphore(max_concurrent) in Chat.__init__ when the value is set and positive
  • Acquires it in _handle_concurrent around _dispatch_to_handlers
  • None / unset keeps unbounded behavior (matches upstream TS default of Infinity)

Divergence note: upstream also doesn't enforce, so Python is leading here. Upstream can mirror the pattern if they want.

Release 0.4.26.1

Bundles this fix with the other landed work for the Python-only follow-up on 0.4.26:

Section Contents
Fixes Slack stream missing await (#44), Teams zero-height divider (#45), max_concurrent enforced (this PR, #51)
Python-specific robustness request.text() duck-type handling (Flask sync support), inspect.isawaitable on sync-or-async handlers
New public APIs Chat.thread() factory (#46), Slack current_token / current_client (#47)
Internals Pyrefly 213 → 0 errors (baseline removed); 3471 tests passing
Known gaps #50, #52, #54, #57, #58, #59, #53 (all linked in CHANGELOG)

Version scheme clarified in CONTRIBUTING.md: .patch bumps during alpha can be additive (fixes + features) since the package is marked Development Status :: 3 - Alpha and pre-1.0 per semver convention. Once we hit 1.0, .patch tightens to fixes-only.

Test plan

  • 2 new regression tests in TestConcurrencyConcurrent:
    • test_max_concurrent_bounds_in_flight_handlers — 5 dispatched, cap 2, observes cap holds
    • test_max_concurrent_none_allows_unbounded — no cap, all 5 run concurrently
  • uv run ruff check src/ tests/ scripts/ — clean
  • uv run ruff format --check src/ tests/ scripts/ — clean
  • uv run python scripts/audit_test_quality.py — 0 hard failures
  • uv run pytest tests/ --tb=short -q3471 passed, 2 skipped

Release sequencing

Depends on PRs #48 (bug fix bundle) and #49 (pyrefly zero-errors) landing first. Once all three are on main:

  1. Verify pyproject.toml shows 0.4.26.1
  2. Tag v0.4.26.1
  3. gh release create v0.4.26.1 --target main --title "v0.4.26.1 — Python-only follow-up on 4.26" → triggers PyPI publish

Closes #51.

Summary by CodeRabbit

  • Bug Fixes

    • Restored Slack native streaming
    • Fixed Teams divider rendering
    • Improved Python streaming robustness
  • New Features

    • Added Chat.thread(...) for worker reconstruction with message context
    • Added SlackAdapter current_token and current_client property accessors
    • Enforced configurable concurrency limits for handler execution
  • Tests

    • Added concurrency tests validating limit enforcement and unbounded behavior
  • Chores

    • Version bumped to 0.4.26.1; updated contributing/versioning guidance and changelog documentation

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Enforces ConcurrencyConfig.max_concurrent via an asyncio.Semaphore for the "concurrent" strategy; validates construction-time values; bumps package to 0.4.26.1; updates changelog, contributing guidance, and UPSTREAM_SYNC; adds tests for concurrency limits and validation.

Changes

Cohort / File(s) Summary
Release & Docs
CHANGELOG.md, CONTRIBUTING.md, pyproject.toml, docs/UPSTREAM_SYNC.md
Bump version to 0.4.26.1; add release notes (Slack streaming await, Teams divider fix, streaming robustness, new public APIs/properties); relax .patch guidance during 0.x alpha; document Python enforcement and validation for ConcurrencyConfig.max_concurrent.
Concurrency Enforcement
src/chat_sdk/chat.py
Validate ConcurrencyConfig.max_concurrent (must be >0 or None and only for strategy == "concurrent"); initialize asyncio.Semaphore when configured; acquire/release around concurrent handler dispatch to bound in-flight handlers; preserve unbounded behavior when unset.
Tests: Concurrency
tests/test_chat_faithful.py
Add tests: enforce max_concurrent cap (peak observed), reject non‑positive values, reject max_concurrent when strategy ≠ "concurrent", and verify unbounded behavior when unset or None.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Chat as Chat
    participant Sem as Semaphore
    participant Handler as Handler

    Client->>Chat: submit message / dispatch
    Chat->>Sem: acquire() (if max_concurrent set)
    Sem-->>Chat: acquired
    Chat->>Handler: dispatch handler (run)
    Handler-->>Chat: completes / errors
    Chat->>Sem: release() (if acquired)
    Sem-->>Chat: released
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hopped in with a semaphore bright,
Counting handlers, day and night.
Streams now wait, dividers align,
Threads restored and tokens shine—
A tiny hop made concurrency right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title concisely summarizes the main change: enforcing ConcurrencyConfig.max_concurrent and releasing version 0.4.26.1, which matches the primary objective of fixing issue #51.
Linked Issues check ✅ Passed The PR addresses all coding requirements from issue #51: asyncio.Semaphore initialization in Chat.init, enforcement in the concurrent strategy path, proper error handling, and comprehensive regression tests for both bounded and unbounded behavior.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives: core fix for max_concurrent enforcement, version bump, release notes, contributing guidelines clarification, and new test coverage. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/max-concurrent-enforcement

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread tests/test_chat_faithful.py Fixed
Comment thread tests/test_chat_faithful.py Fixed
Comment thread tests/test_chat_faithful.py Fixed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/chat_sdk/chat.py`:
- Around line 306-310: The current initialization of self._concurrent_semaphore
silently treats _concurrency_max_concurrent <= 0 as None which re-enables
unbounded concurrency; instead validate _concurrency_max_concurrent (the
constructor/initializer that sets _concurrency_max_concurrent) and raise a clear
ValueError for zero or negative values, only allowing None for "no limit" via an
explicit None input and creating
asyncio.Semaphore(self._concurrency_max_concurrent) when a positive integer is
provided; update any docs/comments and tests referencing
_concurrency_max_concurrent or _concurrent_semaphore accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 337e95e4-7062-4f02-ae19-03841c69d287

📥 Commits

Reviewing files that changed from the base of the PR and between 6292436 and 4427c97.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • CONTRIBUTING.md
  • pyproject.toml
  • src/chat_sdk/chat.py
  • tests/test_chat_faithful.py

Comment thread src/chat_sdk/chat.py
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request bumps the version to 0.4.26.1 and introduces several fixes and features, most notably the enforcement of max_concurrent for the concurrent strategy using an asyncio.Semaphore. Feedback suggests that this limit should be applied globally across all event types and strategies to ensure consistent resource bounding. Additionally, it is recommended to consider lazy initialization for the semaphore to better support multi-event loop environments.

Comment thread src/chat_sdk/chat.py
Comment thread src/chat_sdk/chat.py
patrick-chinchill added a commit that referenced this pull request Apr 23, 2026
- Raise `ValueError` on `max_concurrent <= 0` instead of silently falling
  back to unbounded (CodeRabbit MAJOR). A user setting `max_concurrent=0`
  likely means "throttle hard" and would be surprised by unlimited
  dispatch otherwise.
- Remove redundant local `import asyncio` in 2 test methods — module is
  already imported at the top of the file (github-code-quality).
- Soften early `assert max_observed == 2` to `<= 2`. The early check is
  about "bound not exceeded during gate-closed phase"; the final `== 2`
  after drain is what asserts the peak was actually reached
  (github-code-quality "redundant comparison").
- New test: `test_max_concurrent_zero_or_negative_raises` covers 0, -1,
  -100.
patrick-chinchill added a commit that referenced this pull request Apr 23, 2026
- **Reject `max_concurrent` with non-concurrent strategy**: if a user
  sets `ConcurrencyConfig(strategy="queue", max_concurrent=5)`, they
  have a misconfig — the field is only honored under `"concurrent"`.
  Upstream accepts silently but we fail-fast so it's never invisible.
  New test `test_max_concurrent_with_non_concurrent_strategy_raises`.
- **Tighten error message**: raw input via `!r` repr instead of private
  `self._concurrency_max_concurrent` attribute; message now also tells
  the user how to fix ("pass None for unbounded").
- **Test robustness**: replace `for _ in range(20): await asyncio.sleep(0)`
  yield-counting loops with `asyncio.wait_for(_reach_cap(), timeout=1.0)`
  polling until the condition holds. Fixed counts can be flaky on slow
  CI when handlers have multiple internal await points.
- **Document divergence in UPSTREAM_SYNC.md**: added row to Known
  Non-Parity table with rationale (upstream accepts the config field
  but never enforces it; we do).
- **Inline breadcrumb comment** `# Divergence from upstream — see
  docs/UPSTREAM_SYNC.md` at the `_concurrent_semaphore` construction
  site, matching the divergence discipline in CLAUDE.md.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_chat_faithful.py`:
- Around line 2362-2404: Update the test so it explicitly covers the case where
the concurrency config is constructed with max_concurrent=None (not just the
unset/default string). Instead of only calling _init_chat(...,
concurrency="concurrent"), parameterize or add a second invocation that passes a
ConcurrencyConfig(max_concurrent=None) (or the equivalent constructor used in
the codebase) to _init_chat and run the same in-flight assertion; keep the rest
of test_max_concurrent_none_allows_unbounded logic identical and reference the
existing test name and the _init_chat helper and ConcurrencyConfig symbol to
locate where to change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: eadd3007-1120-4f09-a52c-7f4c116f6ada

📥 Commits

Reviewing files that changed from the base of the PR and between 3381b12 and 8d30052.

📒 Files selected for processing (1)
  • tests/test_chat_faithful.py

Comment thread tests/test_chat_faithful.py
Copy link
Copy Markdown
Collaborator Author

@patrick-chinchill patrick-chinchill left a comment

Choose a reason for hiding this comment

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

Re-reviewed current head 97cc314. This looks good to merge. Focused verification passed: uv run pytest tests/test_chat_faithful.py -q -k max_concurrent (5 passed). I couldn't submit an approval because GitHub does not allow approving your own pull request.

Fixes #51. Upstream TS accepts the config field but never enforces it
(3 writes, 0 reads). Python now uses an asyncio.Semaphore so a consumer
setting `concurrency=ConcurrencyConfig(strategy="concurrent",
max_concurrent=N)` actually gets an N-way cap on in-flight handlers.

Divergence from upstream — Python leads here. The upstream behavior is
documented ("Default: Infinity") but not implemented; our enforcement
matches the documented contract.

Changes:
- `Chat.__init__` constructs `asyncio.Semaphore(max_concurrent)` when the
  value is set and positive, leaves it `None` otherwise (unbounded).
- `_handle_concurrent` acquires the semaphore around `_dispatch_to_handlers`
  when present; falls through to direct dispatch when `None`.
- 2 new tests in `TestConcurrencyConcurrent`:
  - `test_max_concurrent_bounds_in_flight_handlers`: 5 messages with
    `max_concurrent=2`; verifies `max_observed == 2` at any time.
  - `test_max_concurrent_none_allows_unbounded`: 5 concurrent handlers
    without a bound all run in parallel (regression guard for the default).

Release prep for 0.4.26.1:
- Version bump `0.4.26` → `0.4.26.1` in pyproject.toml
- `UPSTREAM_PARITY` stays `4.26.0` (still synced to 4.26.0 upstream;
  no new upstream sync in this release)
- CHANGELOG entry with Fixes / Python-specific / New public APIs /
  Internals / Known gaps sections
- CONTRIBUTING.md version-scheme wording: `.patch` bumps during alpha can
  be additive (fixes + features). Tightens to "fixes-only" once we hit 1.0.

Test plan:
- `uv run ruff check src/ tests/ scripts/` — clean
- `uv run ruff format --check src/ tests/ scripts/` — clean
- `uv run python scripts/audit_test_quality.py` — 0 hard failures
- `uv run pytest tests/ --tb=short -q` — 3471 passed, 2 skipped
- Raise `ValueError` on `max_concurrent <= 0` instead of silently falling
  back to unbounded (CodeRabbit MAJOR). A user setting `max_concurrent=0`
  likely means "throttle hard" and would be surprised by unlimited
  dispatch otherwise.
- Remove redundant local `import asyncio` in 2 test methods — module is
  already imported at the top of the file (github-code-quality).
- Soften early `assert max_observed == 2` to `<= 2`. The early check is
  about "bound not exceeded during gate-closed phase"; the final `== 2`
  after drain is what asserts the peak was actually reached
  (github-code-quality "redundant comparison").
- New test: `test_max_concurrent_zero_or_negative_raises` covers 0, -1,
  -100.
- **Reject `max_concurrent` with non-concurrent strategy**: if a user
  sets `ConcurrencyConfig(strategy="queue", max_concurrent=5)`, they
  have a misconfig — the field is only honored under `"concurrent"`.
  Upstream accepts silently but we fail-fast so it's never invisible.
  New test `test_max_concurrent_with_non_concurrent_strategy_raises`.
- **Tighten error message**: raw input via `!r` repr instead of private
  `self._concurrency_max_concurrent` attribute; message now also tells
  the user how to fix ("pass None for unbounded").
- **Test robustness**: replace `for _ in range(20): await asyncio.sleep(0)`
  yield-counting loops with `asyncio.wait_for(_reach_cap(), timeout=1.0)`
  polling until the condition holds. Fixed counts can be flaky on slow
  CI when handlers have multiple internal await points.
- **Document divergence in UPSTREAM_SYNC.md**: added row to Known
  Non-Parity table with rationale (upstream accepts the config field
  but never enforces it; we do).
- **Inline breadcrumb comment** `# Divergence from upstream — see
  docs/UPSTREAM_SYNC.md` at the `_concurrent_semaphore` construction
  site, matching the divergence discipline in CLAUDE.md.
Review caught: between `_reach_cap` returning and the `assert in_flight
== 2` line, a third task could briefly reach the semaphore-gated
handler on a fast multi-core runner. The 10-yield loop that followed
was also vulnerable.

`max_observed` is captured inside the handler under the cooperative
scheduler — it's the atomic signal for 'did the bound leak'. The
final `assert max_observed == 2` after all tasks drain is the
authoritative check.

Removing the point-in-time assertions eliminates the race without
losing coverage.
…t max_concurrent=None

CodeRabbit minor: the test only verified the string form ('concurrent');
adding the ConcurrencyConfig(max_concurrent=None) case proves the
explicit null path also stays unbounded. The two take different code
paths in Chat.__init__ (string branch → defaults all fields; dataclass
branch → reads the field), so both need coverage.
Self-review caught my over-correction. asyncio uses a single-threaded
cooperative scheduler — tasks 3-5 are parked on semaphore.acquire()
and can't interleave between '_reach_cap' returning and the next
synchronous assertion. 'in_flight == 2' IS stable there.

Keeping max_observed == 2 as the primary signal + in_flight == 2 as
a snapshot check while the gate is closed gives stronger coverage
than either alone.

Also: added ids=['string', 'config_none'] to the unbounded parametrize.
Without it pytest falls back to 'concurrency_value1' for the
ConcurrencyConfig case, which is opaque in CI logs.
@patrick-chinchill patrick-chinchill force-pushed the fix/max-concurrent-enforcement branch from 97cc314 to ff10803 Compare April 23, 2026 22:37
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/test_chat_faithful.py (1)

2269-2417: Add an exception-path regression for semaphore release.

These tests cover the steady-state cap and constructor validation, but not the "release on error" half of the feature. If _handle_concurrent() is ever rewritten away from async with, a leaked permit after one failing handler would slip past this suite.

🧪 Suggested test
+    async def test_max_concurrent_releases_permit_on_handler_error(self):
+        state = create_mock_state()
+        adapter = create_mock_adapter("slack")
+
+        chat, _, _ = await _init_chat(
+            adapter=adapter,
+            state=state,
+            concurrency=ConcurrencyConfig(strategy="concurrent", max_concurrent=1),
+        )
+
+        first_started = asyncio.Event()
+        second_started = asyncio.Event()
+        seen: list[str] = []
+
+        `@chat.on_mention`
+        async def handler(thread, message, context=None):
+            seen.append(message.id)
+            if message.id == "msg-1":
+                first_started.set()
+                raise RuntimeError("boom")
+            second_started.set()
+
+        first = asyncio.create_task(
+            chat.handle_incoming_message(
+                adapter,
+                "slack:C123:1",
+                create_test_message("msg-1", "Hey `@slack-bot`"),
+            )
+        )
+        await asyncio.wait_for(first_started.wait(), timeout=1.0)
+        await asyncio.gather(first, return_exceptions=True)
+
+        await chat.handle_incoming_message(
+            adapter,
+            "slack:C123:2",
+            create_test_message("msg-2", "Hey `@slack-bot`"),
+        )
+        await asyncio.wait_for(second_started.wait(), timeout=1.0)
+        assert seen == ["msg-1", "msg-2"]

As per coding guidelines, "Every test must fail when the code is wrong."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_chat_faithful.py` around lines 2269 - 2417, The suite is missing a
regression test that a semaphore permit is released when a handler raises in
_handle_concurrent(); add a test that configures
ConcurrencyConfig(strategy="concurrent", max_concurrent=1), registers an
`@chat.on_mention` handler that acquires the gate then raises an exception,
dispatches two messages via chat.handle_incoming_message such that the first
acquires the semaphore and fails, then verify the second is able to acquire the
semaphore and complete (e.g., use Events/gate to control timing and assert
finished count and max_observed to ensure no permit leak). Ensure the test fails
if permits are not released (i.e., second task blocks forever or max_observed
stays 1).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/UPSTREAM_SYNC.md`:
- Line 460: Update the docs row to state that only non-None values for
max_concurrent are rejected when strategy != "concurrent" (i.e.,
max_concurrent=None is allowed for "queue"/"debounce"/"drop"); mention that when
strategy=="concurrent" a non-None max_concurrent is enforced via
asyncio.Semaphore to bound in-flight handlers and that the field is a no-op if
left as None. Reference the config field name max_concurrent and the strategy
string "concurrent" and the check in src/chat_sdk/chat.py that raises only when
max_concurrent is not None and strategy != "concurrent".

In `@src/chat_sdk/chat.py`:
- Around line 318-333: The code currently only checks
self._concurrency_max_concurrent <= 0 but does not reject non-integer types
(floats, bools, strings) which can lead to incorrect Semaphore behavior; add an
explicit type validation for self._concurrency_max_concurrent before the <= 0
check: require it to be an actual int (reject bool since bool is subclass of
int) by using either type(self._concurrency_max_concurrent) is int or
(isinstance(self._concurrency_max_concurrent, int) and not
isinstance(self._concurrency_max_concurrent, bool)); if the check fails raise a
ValueError that includes the offending value (raw_max) and a clear message; keep
the subsequent <= 0 check and the Semaphore creation
(asyncio.Semaphore(self._concurrency_max_concurrent)) unchanged otherwise.

---

Nitpick comments:
In `@tests/test_chat_faithful.py`:
- Around line 2269-2417: The suite is missing a regression test that a semaphore
permit is released when a handler raises in _handle_concurrent(); add a test
that configures ConcurrencyConfig(strategy="concurrent", max_concurrent=1),
registers an `@chat.on_mention` handler that acquires the gate then raises an
exception, dispatches two messages via chat.handle_incoming_message such that
the first acquires the semaphore and fails, then verify the second is able to
acquire the semaphore and complete (e.g., use Events/gate to control timing and
assert finished count and max_observed to ensure no permit leak). Ensure the
test fails if permits are not released (i.e., second task blocks forever or
max_observed stays 1).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c765cd6-3d4e-4891-b6d6-7defb34f71f3

📥 Commits

Reviewing files that changed from the base of the PR and between 8d30052 and ff10803.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • CONTRIBUTING.md
  • docs/UPSTREAM_SYNC.md
  • pyproject.toml
  • src/chat_sdk/chat.py
  • tests/test_chat_faithful.py
✅ Files skipped from review due to trivial changes (1)
  • pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (2)
  • CONTRIBUTING.md
  • CHANGELOG.md

Comment thread docs/UPSTREAM_SYNC.md Outdated
Comment thread src/chat_sdk/chat.py
- Reject non-integer `max_concurrent` (float, bool, str) at construction
  instead of deferring to `asyncio.Semaphore` behavior — `1.5` silently
  drives the counter negative; `True` allocates a 1-way bound from a
  bool; `"2"` raises the wrong exception. CodeRabbit flagged.
- Clarify the `ConcurrencyConfig.max_concurrent` divergence row in
  UPSTREAM_SYNC: rejection only fires when the field is non-`None`;
  `max_concurrent=None` stays compatible with every strategy.
- Added `test_max_concurrent_non_integer_raises` covering six bad-type
  cases (float, True, False, str, 0.0, list).
- Refresh stale test count (3544 → 3545).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.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.

ConcurrencyConfig.max_concurrent is unused — enforce it per upstream

1 participant