Summary
Single-module pdd sync <basename> prints Overall status: Failed (red) in the final summary panel but exits with code 0. CI, scripts, and agentic child runners that check the exit code read the failed sync as success.
Found during the non-billable agentic-sync E2E validation (epic PR #1868); re-confirmed 2026-07-07 against a write-mode failure scenario: pdd sync on a module whose generate step failed printed Overall status: Failed and exited 0.
Where
pdd/sync_main.py:1387 — computes overall_success, prints it in the summary panel, and returns it inside aggregated_results["overall_success"].
pdd/commands/maintenance.py (sync command, ~line 514) — receives the result and does return str(result), total_cost, model_name unconditionally. No non-zero exit when overall_success is False.
Why it matters
- Downstream tooling has already grown a workaround:
pdd/agentic_sync_runner.py:2806 does a "sticky capture" scrape of the child's stdout for Overall status: ... Failed because it cannot trust the exit code. Direct invocations (CI gates, shell scripts, && chains) have no such protection.
- This blocks trusting any benchmark or GO/NO-GO evaluation of sync modes that keys off exit codes.
- Note: the issue-URL agentic parent path exits non-zero correctly; this bug is specific to the single-module path.
Established convention
The #1677 fix (AmbiguousModuleError in the same command) sets the pattern: print the error, then raise click.exceptions.Exit(1) so quiet runs and agentic child runners never read failure as success.
Acceptance criteria
Refs: epic PR #1868 (validation that found this), #1677 (exit-code convention).
Summary
Single-module
pdd sync <basename>printsOverall status: Failed(red) in the final summary panel but exits with code 0. CI, scripts, and agentic child runners that check the exit code read the failed sync as success.Found during the non-billable agentic-sync E2E validation (epic PR #1868); re-confirmed 2026-07-07 against a write-mode failure scenario:
pdd syncon a module whose generate step failed printedOverall status: Failedand exited 0.Where
pdd/sync_main.py:1387— computesoverall_success, prints it in the summary panel, and returns it insideaggregated_results["overall_success"].pdd/commands/maintenance.py(synccommand, ~line 514) — receives the result and doesreturn str(result), total_cost, model_nameunconditionally. No non-zero exit whenoverall_successisFalse.Why it matters
pdd/agentic_sync_runner.py:2806does a "sticky capture" scrape of the child's stdout forOverall status: ... Failedbecause it cannot trust the exit code. Direct invocations (CI gates, shell scripts,&&chains) have no such protection.Established convention
The #1677 fix (
AmbiguousModuleErrorin the same command) sets the pattern: print the error, thenraise click.exceptions.Exit(1)so quiet runs and agentic child runners never read failure as success.Acceptance criteria
pdd sync <basename>exits non-zero when the aggregated result hasoverall_success == False.--dry-runreporting-only runs that succeed.Overall status:stdout contract is unchanged (the runner's sticky capture must keep working).Refs: epic PR #1868 (validation that found this), #1677 (exit-code convention).