Skip to content

Epic: agentic-sync default-readiness fixes (#1979 exit codes, #1980 scope reconciliation)#1981

Draft
sohni-tagirisa wants to merge 2 commits into
mainfrom
epic/agentic-sync-default-readiness
Draft

Epic: agentic-sync default-readiness fixes (#1979 exit codes, #1980 scope reconciliation)#1981
sohni-tagirisa wants to merge 2 commits into
mainfrom
epic/agentic-sync-default-readiness

Conversation

@sohni-tagirisa

@sohni-tagirisa sohni-tagirisa commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Epic: agentic-sync default-readiness fixes

Integration branch for the two remaining correctness blockers identified by the agentic-sync E2E validation (epic #1868) before the agentic-vs-regular default sync evaluation can be trusted:

Sub-PR Issue Fix
#1982 (merged) #1979 pdd sync <basename> exits non-zero when Overall status: Failed
#1983 (in review) #1980 Branch-diff fast path reconciles diff-detected scope with the issue's explicitly requested modules

Why these two

Both bugs make failure look like success, which poisons any exit-code- or status-based comparison of regular vs agentic sync:

Process

Each fix ships as a TDD sub-PR (failing test committed and pushed before the fix) into this branch, with an adversarial Codex review loop per docs/runbooks/pr-loop-process.md.

Do not merge this epic into main without maintainer review.

🤖 Generated with Claude Code

Epic integration branch for the fixes blocking the agentic-sync
default GO/NO-GO evaluation:

- #1979: pdd sync (single-module) exits 0 despite 'Overall status: Failed'
- #1980: agentic sync branch-diff fast path silently under-scopes vs
  the issue's explicitly requested modules

Sub-PRs target this branch. Do not merge this PR into main without
maintainer review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…us Failed (#1979) (#1982)

* test(sync): failing tests for single-module sync exiting 0 on Overall status Failed (#1979)

Reproduces issue #1979: pdd sync <basename> prints 'Overall status: Failed'
but exits 0, so CI, shell && chains, and agentic child runners read a failed
sync as success. Red: 4 failure-path tests fail (exit_code == 0), 2
success-path tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(sync): exit non-zero when single-module sync reports Overall status Failed (#1979)

The single-module path of the sync command returned the cost-tracking tuple
(str(result), total_cost, model_name) unconditionally, so a sync whose
aggregated result had overall_success == False printed 'Overall status:
Failed' but exited 0. CI, shell && chains, and the agentic child runners
(which key off the exit code) read the failed sync as success; the runner
already grew a sticky stdout scrape as a workaround.

Follow the #1677 / dispatch-helper convention in the same command: raise
click.exceptions.Exit(1) on failure, placed after the evidence-manifest
write so failure evidence is still recorded. Also widen the re-raise
clause to (click.Abort, click.exceptions.Exit) — the existing broad
'except Exception' would otherwise swallow the Exit (it subclasses
RuntimeError) and turn it back into exit 0, matching the guard the
agentic/global dispatch helpers already carry.

The 'Overall status:' stdout contract is unchanged (sync_main untouched);
successful runs, including successful --dry-run, still exit 0.

Fixes #1979

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(sync): failed sync must still write the track_cost CSV row while exiting non-zero (#1979)

Codex review of PR #1982 (P1): raising click.exceptions.Exit(1) makes
track_cost capture the exception and skip the --output-cost /
PDD_OUTPUT_COST_PATH row (it only writes when exception_raised is None).
The agentic runner sets PDD_OUTPUT_COST_PATH for child syncs and parses
the row to accumulate cost and enforce --budget, so failed attempts must
keep recording their cost.

Red: test_cli_sync_failure_still_writes_cost_csv_row fails (exit is
non-zero but no CSV row is written). The companion success-path guard
(exactly one row, no double-write) passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(sync): keep writing the track_cost CSV row when a failed sync exits non-zero (#1979)

Codex review of PR #1982 (P1): the Exit(1) raised for
overall_success == False was captured by @track_cost as exception_raised,
and the wrapper only wrote the --output-cost / PDD_OUTPUT_COST_PATH row
when no exception was raised — so exactly the failed child attempts lost
their cost row, breaking the agentic runner's cost accumulation and
--budget enforcement (pdd/agentic_sync_runner.py sets
PDD_OUTPUT_COST_PATH per child and parses the row).

Mechanism chosen: stash the (str(result), total_cost, model_name) tuple
on ctx.obj under track_cost.EXIT_COST_RESULT_KEY right before raising,
and teach the wrapper to write the row from that stash when the captured
exception is an intentional click.exceptions.Exit. The alternative — not
raising in the command and exiting from a group-level result hook — was
rejected because the server's click_executor invokes commands directly
via ctx.invoke (bypassing any group hook) and child processes get their
exit code from click standalone mode, so only a raise inside the command
covers both paths.

Behavior preserved:
- success path writes exactly ONE row (stash is only set on the failure
  raise; the wrapper pops it unconditionally so it can never leak into a
  later tracked command, mirroring the attempted_models handling)
- #1677 AmbiguousModuleError and the agentic/global dispatch failure
  raises carry no stash, so they keep skipping the row as before
- crashes / other exceptions keep skipping the row as before

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(sync): failing test for core-dump stream capture leak on failed-sync Exit; make cost-CSV tests hermetic (#1979)

Codex round-2 review of PR #1982 (2 P2):

P2#1 red test: a failed sync driven through cli.main(standalone_mode=False)
with --core-dump must leave sys.stdout/sys.stderr as the original streams.
The non-zero branch of PDDCLI.invoke's 'except click.exceptions.Exit'
re-raises without _restore_captured_streams(ctx), so embedded top-level
invocations (server executor uses ctx.invoke in-process) leak
OutputCapture. Red: AssertionError 'failed sync leaked the core-dump
OutputCapture onto sys.stdout'.

P2#2 (test-only fix): _enable_cost_csv() deletes PYTEST_CURRENT_TEST
process-wide, which re-enables onboarding shell detection (subprocess
'ps') — the two cost-CSV tests failed in sandboxes unless
PDD_SUPPRESS_SETUP_REMINDER was set externally. Set it via monkeypatch in
the helper; verified with env -u PDD_SUPPRESS_SETUP_REMINDER pytest (2
passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cli): restore core-dump captured streams before re-raising non-zero Exit (#1979)

Codex round-2 review of PR #1982 (P2#1): the 'except click.exceptions.Exit'
branch in PDDCLI.invoke re-raised intentional non-zero exits (failed sync,
#1677 ambiguous module, checkup --validate-arch-includes) WITHOUT restoring
the --core-dump OutputCapture wrappers. One-shot CLI processes die anyway,
but embedded top-level invocations — cli.main(standalone_mode=False) and
the in-process server executor (ctx.invoke) — were left with
sys.stdout/sys.stderr still wrapped for the rest of the process.

Call the existing _restore_captured_streams(ctx) helper (written for
exactly this, and already used by the ctx.exit(0) early-exit paths and the
duplicate-guard re-raises) before the re-raise. The SystemExit branch
above already restores inline and is left untouched; exit_code == 0 Exits
keep propagating through Click's normal cleanup as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <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.

1 participant