Skip to content

fix: classify adapter dispatch outcomes#75

Open
Pigbibi wants to merge 21 commits into
mainfrom
codex/dispatch-outcome-adapters
Open

fix: classify adapter dispatch outcomes#75
Pigbibi wants to merge 21 commits into
mainfrom
codex/dispatch-outcome-adapters

Conversation

@Pigbibi

@Pigbibi Pigbibi commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Scope

Vertical dispatch-outcome prerequisite for AI Budget Guard PR #73. It includes only Codex/LLM adapter outcomes plus the gateway persistence, triage, and review-consumption boundary required to make those outcomes effective. Budget ledgers, monthly caps, and settlement implementation remain in #73.

Stable contract

  • A failure proven by code structure before invocation is not_dispatched.
  • A received provider response or accepted result is dispatched.
  • An invoked subprocess/HTTP call without a trustworthy structured outcome is pending_uncertain; non-zero CLI exits and stderr text are not release evidence.
  • pending_uncertain is persisted, classified non-retryable, and forces review escalation rather than auto_pr or auto_merge.

Validation

  • python3 -m pytest -q — 674 passed, 1 skipped, 16 subtests passed
  • python3 -m ruff check service scripts tests
  • actionlint
  • python3 -m compileall -q service scripts

Co-Authored-By: Codex <noreply@openai.com>
@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

🤖 Codex PR Review

🚫 Merge blocked: 3 serious issue(s) found in high-risk files

⚖️ Codex Review Arbitration

🚫 block: service/adapters/codex_adapter.py still has two proven blockers in the cumulative diff. First, execute() switched from subprocess.run(..., input=prompt, ...) to subprocess.Popen(...) plus process.communicate(input=prompt, ...), but the new Popen call does not configure stdin=subprocess.PIPE, so the child has no explicit stdin pipe for that prompt path. Second, after Popen succeeds, on_dispatch_start() is invoked before communicate(), and both the except OSError and broad except Exception paths return a failed CodexResult without any kill()/wait()/communicate() cleanup, so a launched codex child can be left running unreaped. The llm_adapter.py finding is not sustained: the new contract in docs/async_service_deployment.md says pending_uncertain is never retried automatically, and _retry_with_backoff() only retries when _should_retry(status) is true; the adapter’s ambiguous transport failures do not carry such a retryable status, so the described 'uncertain failure then later success' path is not supported by the current source of truth. No current suggestion contradicts the prior trusted finding.

🚫 Blocking Issues

These issues must be fixed before this PR can be merged:

1. 🟠 [HIGH] Bug in service/adapters/codex_adapter.py

The new subprocess.Popen(...) call does not set stdin=subprocess.PIPE, so process.communicate(input=prompt, ...) no longer delivers the review prompt to codex exec. In production this can make Codex read EOF / inherited service stdin instead of the intended prompt, causing empty reviews, hangs, or universal dispatch_uncertain_failure results. (line 140)

Suggestion: Create the process with stdin=subprocess.PIPE (or switch back to subprocess.run(..., input=prompt, ...)) so the prompt is actually written to the child process, and add a regression test that asserts the prompt reaches the subprocess.

2. 🟠 [HIGH] Reliability in service/adapters/codex_adapter.py

If on_dispatch_start() raises after Popen succeeds, the generic exception handler returns a failed CodexResult without terminating or reaping the child process. That leaves a live codex subprocess running after the job has already been marked failed, which can leak CPU/RAM on this small VPS and continue an untracked remote dispatch. (line 152)

Suggestion: When any exception happens after Popen succeeds but before normal completion, always terminate the child and wait for it before returning. A small helper that calls kill()/communicate() in every post-launch error path would fix this cleanly.

3. 🟠 [HIGH] Logic in service/adapters/llm_adapter.py

complete() sets dispatch_started=not dispatch_uncertain on successful calls. After a retry sequence like 'ambiguous transport failure, then successful provider response', the final result is persisted as dispatch_started=False, dispatch_uncertain=True even though a later response proves that at least one request definitely reached the provider. That corrupts the independent dispatch facts this PR is trying to preserve and can mislead later reconciliation/billing logic. (line 402)

Suggestion: Have _retry_with_backoff() return both accumulated dispatch_started and dispatch_uncertain flags, and on success persist dispatch_started=True whenever any attempt produced a real provider response (or simply whenever the successful final attempt returned structured output).


Review by Codex PR Review bot • PR

@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: fb20c76c58

ℹ️ 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 thread service/adapters/llm_adapter.py Outdated
Comment on lines +310 to +311
dispatch_started=False,
dispatch_uncertain=_dispatch_is_uncertain(str(exc)),

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 dispatch state for provider responses

When the provider returns a successful HTTP response but the adapter rejects the payload (for example _openai_completion raises OpenAI returned empty choices/empty content, or _anthropic_completion raises returned no text content), the request has already reached the provider, but this catch-all marks dispatch_started=False and only infers uncertainty from the error string. Any caller using the new flags to decide whether work was accepted can treat a confirmed provider response as not dispatched/pending and retry or release capacity incorrectly; distinguish pre-request failures from post-response parsing/content failures and set the dispatch state accordingly.

Useful? React with 👍 / 👎.

Co-Authored-By: Codex <noreply@openai.com>

@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: 324ec80afb

ℹ️ 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 thread service/adapters/llm_adapter.py Outdated
Comment on lines +174 to +177
except (urllib.error.URLError, OSError, ValueError) as exc:
raise LlmAdapterError(f"OpenAI network error: {exc}") from exc
raise LlmAdapterError(
f"OpenAI network error: {exc}", dispatch_uncertain=True
) from exc

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 Mark post-response JSON failures as dispatched

Fresh evidence in this patch is that ValueError from json.loads(resp.read().decode(...)) is still grouped with transport failures here, and the Anthropic path has the same catch. When the provider returns a 200 response with malformed/non-UTF8 JSON, the request has definitely reached the provider, but this reports dispatch_uncertain=True and dispatch_started=False; callers using these new flags can treat consumed provider work as only pending/ambiguous and retry or release capacity incorrectly. Split JSON/decode failures after urlopen succeeds into a dispatched parse error.

Useful? React with 👍 / 👎.

Pigbibi and others added 2 commits July 12, 2026 08:47
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>

@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: da24f81eb9

ℹ️ 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 thread service/adapters/llm_adapter.py Outdated
Comment on lines +167 to +168
with urllib.request.urlopen(req, timeout=timeout) as resp:
payload = json.loads(resp.read().decode("utf-8"))
raw = resp.read()

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 Mark response-body read failures as dispatched

When the provider has already returned response headers but the socket times out or resets during resp.read(), this line is still inside the same try whose except (URLError, OSError) reports only dispatch_uncertain=True with dispatch_started=False. In that scenario the request has definitely reached the provider, so callers using these new flags can retry or release capacity as if no provider work was consumed; read failures after entering the response context should be classified as dispatched (the Anthropic path has the same pattern).

Useful? React with 👍 / 👎.

Pigbibi and others added 4 commits July 12, 2026 09:27
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>

@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: 4f85bea24a

ℹ️ 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".

return CodexResult(
success=False,
error=f"codex exec failed (rc={completed.returncode})" + (f":\n{detail}" if detail else ""),
dispatch_uncertain=True,

This comment was marked as off-topic.

Comment thread service/adapters/llm_adapter.py Outdated
payload = json.loads(resp.read().decode("utf-8"))
raw = resp.read()
except urllib.error.HTTPError as exc:
detail = _scrub_api_keys(exc.read().decode("utf-8", errors="replace")[:500])

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 dispatch state when HTTP error bodies cannot be read

When the provider has already returned an HTTP error but the socket resets or times out while reading the error body, exc.read() raises before this block wraps the error with dispatch_started=True and status_code; _retry_with_backoff then sees only a raw OSError, reports dispatch_uncertain=True/dispatch_started=False, and also loses retryability for 429/5xx. The request definitely reached the provider once HTTPError exists, so the dispatch state and status should be preserved even if the error body is unreadable; the Anthropic handler has the same pattern.

Useful? React with 👍 / 👎.

Pigbibi and others added 3 commits July 12, 2026 10:47
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>

@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: 317c3152c1

ℹ️ 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".

model=resolved_model,
output=output,
latency_seconds=time.time() - started,
dispatch_started=not dispatch_uncertain,

This comment was marked as off-topic.

Comment thread service/adapters/llm_adapter.py Outdated
payload = json.loads(resp.read().decode("utf-8"))
try:
raw = resp.read()
except (OSError, TimeoutError) as exc:

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 Catch truncated provider bodies as dispatched

Fresh evidence in this version is that the new read handler still only catches OSError/TimeoutError; Python's HTTPResponse.read() can raise http.client.IncompleteRead when the provider has sent headers but the body is truncated, and that class is not an OSError. In that scenario (same pattern in the Anthropic read path) the raw exception bypasses _retry_with_backoff/LlmAdapterError, so /v1/ai/analyze can turn a confirmed provider dispatch into an internal error with no dispatch flags; catch IncompleteRead/HTTPException here and mark it dispatch_started=True.

Useful? React with 👍 / 👎.

Co-Authored-By: Codex <noreply@openai.com>

@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: cb52d33343

ℹ️ 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 thread service/adapters/llm_adapter.py Outdated
reason = exc.reason if isinstance(exc, urllib.error.URLError) else exc
if isinstance(reason, TimeoutError):
return True
if isinstance(reason, (socket.gaierror, ConnectionRefusedError, ssl.SSLError)):

This comment was marked as off-topic.

Pigbibi and others added 2 commits July 12, 2026 12:07
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>

@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: 06549610dc

ℹ️ 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 thread service/adapters/llm_adapter.py Outdated
def _http_error_detail(exc: urllib.error.HTTPError) -> str:
try:
return _scrub_api_keys(exc.read().decode("utf-8", errors="replace")[:500])
except (OSError, TimeoutError):

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 Catch truncated HTTP error bodies

Fresh evidence in this patch is that _http_error_detail still only catches OSError/TimeoutError; if a provider returns an HTTP 429/5xx but the error body is truncated, HTTPError.read() can raise http.client.IncompleteRead (HTTPException, not OSError). That escapes before the code wraps the response with dispatch_started=True and status_code, so /v1/ai/analyze can turn a confirmed provider dispatch into an internal error and skip the intended retry/backoff for the 429/5xx; catch HTTPException here and fall back to the placeholder detail.

Useful? React with 👍 / 👎.

Pigbibi and others added 4 commits July 12, 2026 12:47
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>

@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: fa48af98e5

ℹ️ 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 thread service/ai_gateway_service.py Outdated
job["error"] = result.error
job["failure_category"] = _classify_failure(result.error)
job["failure_category"] = (
"dispatch_uncertain" if dispatch_state == "pending_uncertain" else _classify_failure(result.error)

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 Recognize dispatch_uncertain as a failure category

When an async Codex job now fails with this new dispatch_uncertain category, downstream paths format it as [...] and then reuse existing category helpers, but those helpers only extract bracketed names ending in _failure and the monthly workflow's infra-category set also omits this value. In the non-timeout uncertain case, such as a nonzero Codex exit, scripts/run_monthly_codex_audit.py will classify Codex audit service job failed [dispatch_uncertain]: ... as unknown_failure and may run the auto-provider fallback, contradicting the new contract that uncertain dispatches are never retried automatically; either make the new category parseable/recognized by those helpers or use an existing _failure category name.

Useful? React with 👍 / 👎.

Co-Authored-By: Codex <noreply@openai.com>

@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: 4132b17026

ℹ️ 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 thread service/ai_gateway_service.py Outdated
job["failure_category"] = "stale_job_timeout"
if previous_status == "running" and str(job.get("dispatch_state") or "") != "not_dispatched":
_apply_dispatch_state(job, "pending_uncertain")
job["failure_category"] = "dispatch_uncertain"

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 Use the recognized uncertain failure category

Fresh evidence in this patch is this stale-running-job branch: it persists failure_category as dispatch_uncertain, while the normal async failure path uses dispatch_uncertain_failure. When a polling client receives this job it formats Codex audit service job failed [dispatch_uncertain]: ..., but scripts/run_monthly_codex_audit.service_failure_category only extracts bracketed categories ending in _failure, so the monthly auto-provider path can classify the stale uncertain dispatch as unknown_failure and run API fallback instead of forcing reconciliation/no automatic retry.

Useful? React with 👍 / 👎.

Comment on lines +1205 to +1207
job["failure_category"] = (
"dispatch_uncertain_failure" if dispatch_state == "pending_uncertain" else _classify_failure(result.error)
)

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 Block uncertain dispatches in automation control

When a job reaches this new dispatch_uncertain_failure path, _record_job_automation_run records its task_state via job_task_state, which still maps this category to plain failed rather than blocked; decide_automation_execution only escalates after max_consecutive_failures failed states, so the very next control snapshot can still allow review_and_fix/auto actions after one pending-uncertain dispatch. Since this category is the signal that accepted work may already exist and must be reconciled, mark it as a blocked/human-review state before feeding it to the automation ledger/control decision.

Useful? React with 👍 / 👎.

Co-Authored-By: Codex <noreply@openai.com>

@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: 2c94bc8184

ℹ️ 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".

raise LlmAdapterError(
f"OpenAI HTTP {exc.code}: {detail}", dispatch_started=True, status_code=exc.code
) from exc
except (urllib.error.URLError, OSError) as exc:

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 header-parse HTTPExceptions as dispatch outcomes

When the provider or an intermediate proxy accepts the POST but returns a malformed status line/header or closes during response-header parsing, urllib.request.urlopen can surface an http.client.HTTPException such as BadStatusLine before the response context is entered. This handler only wraps URLError/OSError, so that exception bypasses LlmAdapterError and the new dispatch flags entirely, turning a pending-uncertain provider invocation into an unclassified internal error; the Anthropic path has the same gap.

Useful? React with 👍 / 👎.

Comment thread service/ai_gateway_service.py Outdated
Comment on lines +1183 to +1186
adapter_invoked = True
_apply_dispatch_state(job, "pending_uncertain")
job["updated_at"] = _now()
_write_job(job)

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 Keep pre-launch callback failures not-dispatched

If _write_job fails inside this callback (for example a transient job-store write error), adapter_invoked has already been set even though subprocess.run has not been called yet. That exception propagates out of CodexAdapter.execute before launching Codex, and the outer handler then records pending_uncertain/dispatch_uncertain_failure for work that was provably never handed to the CLI; move this marker after the pending state is persisted or distinguish callback persistence failures from actual invocation.

Useful? React with 👍 / 👎.

Comment on lines +1206 to +1208
job["failure_category"] = (
"dispatch_uncertain_failure" if dispatch_state == "pending_uncertain" else _classify_failure(result.error)
)

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 Block direct fallback for uncertain Codex jobs

When a Codex job fails with this new dispatch_uncertain_failure category and an error like codex exec failed, scripts/run_codex_pr_review._service_review_should_fallback still matches the codex exec failed fallback signal and immediately runs the direct API fallback. That automatically retries a pending-uncertain dispatch despite the new contract that these outcomes require reconciliation first, so this category needs to be excluded from client fallback before it is emitted here.

Useful? React with 👍 / 👎.

Comment on lines +620 to +621
self.assertFalse(
is_service_infrastructure_failure("Codex audit service job failed [dispatch_uncertain_failure]: timeout")

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 Treat uncertain monthly dispatches as non-fallback failures

Fresh evidence in this version is this new expectation that dispatch_uncertain_failure is not a service infrastructure failure. In the monthly workflow, run_codex_service returns exit code 1 for non-infra categories and the provider == "auto" branch later falls through to run_auto_provider_fallback, so a job reported as pending-uncertain (for example a nonzero Codex exit after possible dispatch) is retried automatically via API fallback instead of requiring reconciliation as the new dispatch contract states.

Useful? React with 👍 / 👎.

Co-Authored-By: Codex <noreply@openai.com>

@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: d162dd741c

ℹ️ 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 +142 to 146
process = subprocess.Popen(
command,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pipe the prompt into the Codex subprocess

With the switch to Popen, communicate(input=prompt) only sends the prompt when the child was created with stdin=subprocess.PIPE; otherwise Codex inherits the service stdin, and _codex_command still passes -, so normal service jobs and the /v1/ai/review Codex verifier read an empty/closed stdin instead of the requested prompt. Add stdin=subprocess.PIPE to preserve the previous subprocess.run(input=prompt) behavior.

Useful? React with 👍 / 👎.

Comment on lines +153 to +157
on_dispatch_start()
completed_stdout, completed_stderr = process.communicate(input=prompt, timeout=timeout)
except subprocess.TimeoutExpired as exc:
return CodexResult(success=False, error=f"codex exec timed out after {timeout}s: {exc}")
except FileNotFoundError as exc:
return CodexResult(success=False, error=f"codex command not found: {exc}")
try:
process.kill()

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 Reap the Codex child on early failures

After Popen succeeds, an on_dispatch_start exception before communicate() returns through the broad handler without terminating the child at all, and the timeout path only sends kill() without waiting/draining. In the async service, a transient job-store write failure or timeout can leave a codex exec process still running or defunct while the job is marked failed/pending, consuming local/provider capacity outside service tracking; terminate and reap the child before returning on these paths.

Useful? React with 👍 / 👎.


def _service_review_should_fallback(exc: ReviewError) -> bool:
message = str(exc).lower()
if "[dispatch_uncertain_failure]" in message:

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 Block fallback after accepted service jobs

This only suppresses fallback when the service returns a categorized dispatch_uncertain_failure, but once run_codex_service_review has accepted a job_id, any later polling URLError/OSError is caught by the next except in run_codex_review_with_fallback and immediately runs direct API fallback. In that scenario the service job may still be queued/running and can dispatch Codex, so the workflow automatically duplicates provider work instead of reconciling the accepted job; track that submission was accepted and do not fall back on post-submit polling failures.

Useful? React with 👍 / 👎.

dispatch_started=bool(job.get("dispatch_started")),
dispatch_uncertain=True,
)
job["failure_category"] = "dispatch_uncertain_failure"

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 Do not resubmit stale uncertain jobs

When a duplicate submission hits a stale running job, this branch now persists dispatch_uncertain_failure, but _find_active_job_by_dedupe_key returns that failed job to _submit_job, whose reuse check only accepts active statuses before creating a fresh job. For a worker that timed out after Codex may have been invoked, a retrying client with the same dedupe key will automatically start a second Codex job despite the new pending-uncertain contract; return the blocked job or otherwise prevent resubmission until reconciliation.

Useful? React with 👍 / 👎.

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