Skip to content

fix: read the etag where the SDK keeps it, and say what the probe is … - #13

Merged
inesaranab merged 1 commit into
mainfrom
fix/detector-probe
Aug 11, 2026
Merged

fix: read the etag where the SDK keeps it, and say what the probe is …#13
inesaranab merged 1 commit into
mainfrom
fix/detector-probe

Conversation

@inesaranab

@inesaranab inesaranab commented Aug 11, 2026

Copy link
Copy Markdown
Owner

The first production run of the readiness change failed with "IfNotModified must be specified with etag". The etag is metadata on a table entity, not one of its properties, so copying the entity into a plain dict dropped it and the conditional write was sent without one.

The test double had put the etag among the properties, so the tests passed against a client that behaves differently from the real one. It now mirrors the SDK, and the test that covers this fails without the fix.

The readiness loop logged nothing, so fifteen minutes of polling and a hang were indistinguishable in the logs. Each attempt is now recorded with how long it has waited and why the endpoint was not usable.

Raises the probe timeout from 30 to 180 seconds. The platform holds a request open while it starts a replica for an app scaled to zero, so a probe that gives up in thirty seconds can abandon that start before a replica exists -- the endpoint is then never reached however often it is retried. 180 stays below the 240-second limit at which ingress severs any request.

The per-job budget is now asserted by a test rather than described in a comment.

Summary by CodeRabbit

  • Improvements

    • Enhanced detector readiness monitoring with detailed progress, timing, and failure information.
    • Increased the detector readiness wait period to improve handling of slow-starting detectors.
    • Extended in-flight queue message visibility to reduce premature retries.
    • Improved job status updates to safely handle concurrent changes.
  • Tests

    • Added coverage for readiness logging, timing safeguards, and conditional job updates.

…doing

The first production run of the readiness change failed with "IfNotModified must
be specified with etag". The etag is metadata on a table entity, not one of its
properties, so copying the entity into a plain dict dropped it and the
conditional write was sent without one.

The test double had put the etag among the properties, so the tests passed
against a client that behaves differently from the real one. It now mirrors the
SDK, and the test that covers this fails without the fix.

The readiness loop logged nothing, so fifteen minutes of polling and a hang were
indistinguishable in the logs. Each attempt is now recorded with how long it has
waited and why the endpoint was not usable.

Raises the probe timeout from 30 to 180 seconds. The platform holds a request
open while it starts a replica for an app scaled to zero, so a probe that gives
up in thirty seconds can abandon that start before a replica exists -- the
endpoint is then never reached however often it is retried. 180 stays below the
240-second limit at which ingress severs any request.

The per-job budget is now asserted by a test rather than described in a comment.
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The changes add structured detector readiness logs, use Azure Table entity metadata for conditional etags, and align detector and queue timeouts. Unit tests cover readiness logging, SDK-style etags, and timeout constraints.

Changes

Worker reliability

Layer / File(s) Summary
Readiness attempt logging
app/adapters/detector_readiness.py, tests/unit/test_detector_readiness.py
wait_until_ready logs probe attempts, failures, retries, success, and deadline exhaustion. Tests verify readiness transition logs.
Azure Table etag handling
app/adapters/job_store_table.py, tests/unit/test_job_store_table.py
fail_if_pending reads etags from entity metadata for conditional updates. The fake table entity now models separate etag metadata.
Worker timeout alignment
app/worker.py, tests/unit/test_worker.py
The detector probe timeout is 180 seconds, and queue visibility is 1,800 seconds. Tests validate the timing bounds.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ 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 clearly describes the ETag fix and readiness probe logging changes, which are central parts of the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/detector-probe

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/worker.py (1)

52-55: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Apply VISIBILITY_TIMEOUT_S to AzureJobQueue.

VISIBILITY_TIMEOUT_S is not passed when main constructs AzureJobQueue. The actual lease therefore remains controlled by the adapter default. A future default change can break the worker budget while the worker constant and its test still pass.

Pass visibility_timeout=int(VISIBILITY_TIMEOUT_S) when constructing AzureJobQueue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/worker.py` around lines 52 - 55, Update the AzureJobQueue construction in
main to pass visibility_timeout=int(VISIBILITY_TIMEOUT_S), ensuring the queue
uses the worker’s configured visibility timeout instead of the adapter default.
tests/unit/test_detector_readiness.py (1)

135-160: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the structured readiness context.

The test verifies event names only. It does not verify attempt, waited_s, or reason. A regression in the new structured log contract would pass this test.

Capture record.context and assert the not-ready attempt contains {"attempt": 1, "waited_s": 0, "reason": "not_serving"}. Also assert the ready event has the expected second-attempt context.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_detector_readiness.py` around lines 135 - 160, Update the
_Collect handler in the readiness test to capture each record’s context
alongside its message, then assert the detector_not_ready_yet event from attempt
1 has attempt 1, waited_s 0, and reason "not_serving". Also assert
detector_ready contains the expected context for attempt 2, including its
attempt and waited_s values and the ready reason.
tests/unit/test_job_store_table.py (1)

127-127: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Make the fake enforce IfNotModified with the ETag.

When match_condition is omitted, Azure SDK defaults to Unconditionally and rejects an explicit ETag. The fake currently accepts the update when the ETag matches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_job_store_table.py` at line 127, Update the fake’s
conditional handling around the etag comparison to enforce IfNotModified
semantics: require an explicit matching condition when an ETag is provided, and
reject requests that omit match_condition even when the ETag matches. Preserve
unconditional updates when no ETag is supplied.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/adapters/detector_readiness.py`:
- Around line 81-90: Update the deadline check in the readiness loop around
now(), waited, and the detector_never_ready branch to use the raw elapsed
duration for retry-budget evaluation, while retaining the rounded waited value
in the context used for logging. Ensure values near the deadline cannot permit
an extra sleep due to rounding.

In `@tests/unit/test_worker.py`:
- Around line 170-175: Update the budget calculation in the relevant worker test
to include DETECTOR_PROBE_TIMEOUT_S alongside DETECTOR_READY_DEADLINE_S and the
existing LLM timeouts, so the assertion verifies the complete worst-case job
duration remains below VISIBILITY_TIMEOUT_S.

---

Nitpick comments:
In `@app/worker.py`:
- Around line 52-55: Update the AzureJobQueue construction in main to pass
visibility_timeout=int(VISIBILITY_TIMEOUT_S), ensuring the queue uses the
worker’s configured visibility timeout instead of the adapter default.

In `@tests/unit/test_detector_readiness.py`:
- Around line 135-160: Update the _Collect handler in the readiness test to
capture each record’s context alongside its message, then assert the
detector_not_ready_yet event from attempt 1 has attempt 1, waited_s 0, and
reason "not_serving". Also assert detector_ready contains the expected context
for attempt 2, including its attempt and waited_s values and the ready reason.

In `@tests/unit/test_job_store_table.py`:
- Line 127: Update the fake’s conditional handling around the etag comparison to
enforce IfNotModified semantics: require an explicit matching condition when an
ETag is provided, and reject requests that omit match_condition even when the
ETag matches. Preserve unconditional updates when no ETag is supplied.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ae54793c-779f-4890-90eb-95809fea5226

📥 Commits

Reviewing files that changed from the base of the PR and between 735d35c and d6941d4.

📒 Files selected for processing (6)
  • app/adapters/detector_readiness.py
  • app/adapters/job_store_table.py
  • app/worker.py
  • tests/unit/test_detector_readiness.py
  • tests/unit/test_job_store_table.py
  • tests/unit/test_worker.py

Comment on lines +81 to +90
waited = round(now() - started)
context = {"attempt": attempt, "waited_s": waited}
if ready:
logger.info("detector_ready", extra={"context": context})
return True
if now() - started + interval_s > deadline_s:
logger.info(
"detector_not_ready_yet", extra={"context": {**context, "reason": reason}}
)
if waited + interval_s > deadline_s:
logger.error("detector_never_ready", extra={"context": context})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use unrounded elapsed time for the deadline check.

Line 81 rounds elapsed time before Line 89 evaluates the retry budget. A value such as 58.6 seconds with a 60 second deadline and a 1 second interval rounds down and permits another sleep after the deadline.

Keep waited_s rounded for logs. Use the raw elapsed value for deadline control.

Proposed fix
-        waited = round(now() - started)
+        elapsed = now() - started
+        waited = round(elapsed)
         context = {"attempt": attempt, "waited_s": waited}
@@
-        if waited + interval_s > deadline_s:
+        if elapsed + interval_s > deadline_s:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
waited = round(now() - started)
context = {"attempt": attempt, "waited_s": waited}
if ready:
logger.info("detector_ready", extra={"context": context})
return True
if now() - started + interval_s > deadline_s:
logger.info(
"detector_not_ready_yet", extra={"context": {**context, "reason": reason}}
)
if waited + interval_s > deadline_s:
logger.error("detector_never_ready", extra={"context": context})
elapsed = now() - started
waited = round(elapsed)
context = {"attempt": attempt, "waited_s": waited}
if ready:
logger.info("detector_ready", extra={"context": context})
return True
logger.info(
"detector_not_ready_yet", extra={"context": {**context, "reason": reason}}
)
if elapsed + interval_s > deadline_s:
logger.error("detector_never_ready", extra={"context": context})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/adapters/detector_readiness.py` around lines 81 - 90, Update the deadline
check in the readiness loop around now(), waited, and the detector_never_ready
branch to use the raw elapsed duration for retry-budget evaluation, while
retaining the rounded waited value in the context used for logging. Ensure
values near the deadline cannot permit an extra sleep due to rounding.

Comment thread tests/unit/test_worker.py
Comment on lines +170 to +175
budget = (
DETECTOR_READY_DEADLINE_S
+ settings.llm_guardrail_timeout_s
+ settings.llm_timeout_s
)
assert budget < VISIBILITY_TIMEOUT_S

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Include the final probe timeout in the job budget.

wait_until_ready checks its deadline after await probe(). A probe that starts just before DETECTOR_READY_DEADLINE_S can still consume DETECTOR_PROBE_TIMEOUT_S. The current assertion omits that time, so it does not prove that one job fits inside the queue lease.

Proposed fix
     budget = (
         DETECTOR_READY_DEADLINE_S
+        + DETECTOR_PROBE_TIMEOUT_S
         + settings.llm_guardrail_timeout_s
         + settings.llm_timeout_s
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
budget = (
DETECTOR_READY_DEADLINE_S
+ settings.llm_guardrail_timeout_s
+ settings.llm_timeout_s
)
assert budget < VISIBILITY_TIMEOUT_S
budget = (
DETECTOR_READY_DEADLINE_S
DETECTOR_PROBE_TIMEOUT_S
settings.llm_guardrail_timeout_s
settings.llm_timeout_s
)
assert budget < VISIBILITY_TIMEOUT_S
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_worker.py` around lines 170 - 175, Update the budget
calculation in the relevant worker test to include DETECTOR_PROBE_TIMEOUT_S
alongside DETECTOR_READY_DEADLINE_S and the existing LLM timeouts, so the
assertion verifies the complete worst-case job duration remains below
VISIBILITY_TIMEOUT_S.

@inesaranab
inesaranab merged commit 6719ba3 into main Aug 11, 2026
2 checks passed
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