Skip to content

Conversation

@beknobloch
Copy link
Contributor

@beknobloch beknobloch commented Jan 8, 2026

Summary

feat: Modified pdd sync to be steerable. Before taking an action, the TUI will present the user with a list of options, including the default option that pdd sync recommends. If no action is selected after a certain timeout length, the default option will be chosen automatically. Steering may be disabled with a new CLI option --no-steer, and the timeout length may be adjusted with a new option --steer-timeout <FLOAT>.
fix: Modified pdd sync TUI behavior to ignore resizes, fixing a previous issue which caused visuals to become corrupted and unreadable when the terminal window was resized.

Test Results

  • Unit tests: PASS
  • Regression tests: PASS
  • Sync regression: PASS
  • Test coverage: 66%

Manual Testing

Manually tested steering, disabling steering, and window resizing to verify stability and usability.

Fixes #169
Fixes #236

@gltanaka gltanaka requested a review from Copilot January 9, 2026 00:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces interactive steering to the PDD sync process via a new --steer flag (disabled by default), and resolves TUI corruption issues caused by terminal resizes by implementing a fixed-width rendering approach during sync runs.

Key changes:

  • Added optional interactive steering allowing users to override sync operation choices at decision points
  • Implemented --steer CLI flag (opt-in) to enable steering without changing default behavior
  • Fixed TUI resize-related visual corruption by freezing animation/layout width at mount time

Reviewed changes

Copilot reviewed 179 out of 184 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
pdd/sync_tui.py Added steering infrastructure (ChoiceScreen, maybe_steer_operation), fixed resize handling with frozen UI width, added on_resize handler
pdd/sync_orchestration.py Integrated steering calls into sync loop, added graceful handling for mock exhaustion in tests
pdd/commands/maintenance.py Added --steer flag with environment variable control
tests/test_sync_tui.py New comprehensive test file with unit tests and Z3 formal verification of steering logic
tests/test_sync_orchestration.py Added missing imports for resize logic testing
tests/test_llm_invoke.py Made model ID matching more robust with fallback candidates and graceful skipping
pdd/fix_code_loop.py Fixed pipe streaming to use readline() instead of single-byte reads, reordered thread joins
pdd/agentic_fix.py Added harvest-first strategy, updated claude binary resolution with shutil.which
README.md Documented --steer flag and interactive steering behavior
Various metadata/backup files Test run results and backup snapshots

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@gltanaka gltanaka left a comment

Choose a reason for hiding this comment

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

please clean out the temp files to make the review easier

Copy link
Contributor

Choose a reason for hiding this comment

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

don't commit these temp files

@beknobloch beknobloch requested a review from gltanaka January 9, 2026 15:10
Copy link
Contributor

@gltanaka gltanaka left a comment

Choose a reason for hiding this comment

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

Thanks for your work. Can you also submit your prompt changes to the pdd_cap?

README.md Outdated
- `--skip-tests`: Skip unit test generation and fixing
- `--target-coverage FLOAT`: Desired code coverage percentage (default is 90.0)
- `--dry-run`: Display real-time sync analysis for this basename instead of running sync operations. This performs the same state analysis as a normal sync run but without acquiring exclusive locks or executing any operations, allowing inspection even when another sync process is active.
- `--steer`: Enable interactive steering during the sync process. When enabled, PDD may pause at key decision points (e.g., choosing the next operation) and allow you to select between multiple valid options instead of automatically choosing one.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a countdown timer or does it just stop until action is taken?
I think having it as a countdown timer might be better?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it uses a countdown timer with a default value of 8 seconds. Currently hardcoded, but I'll work on making it configurable via CLI

calculator.py Outdated
Copy link
Contributor

Choose a reason for hiding this comment

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

should you put this example in examples/ dir instead of root?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the change right now is in the context/ dir with other examples, which seems to be the default. Should I move it elsewhere?

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe make steer the default and have no steer if the user doesn't want delays

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, I'll make that change

@@ -54,11 +54,6 @@ You are operating in an isolated git worktree at: {worktree_path}
This worktree is already checked out to branch `fix/issue-{issue_number}`.
Do NOT create a new branch - just stage, commit, and push.

% Files to Stage
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the purpose of this removal?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the LLM did this at some point as part of syncing sync_tui. I didn't know what the intention was, but does it seem like an error to you?

Copy link
Contributor

Choose a reason for hiding this comment

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

use the latest pdd version so that this is hashes vs. timestamps

@beknobloch
Copy link
Contributor Author

Just submitted the prompt changes to pdd_cap as a PR in the branch pr-267-ben!

@beknobloch beknobloch requested a review from gltanaka January 11, 2026 23:34
@gltanaka gltanaka requested a review from Copilot January 12, 2026 17:56
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jiaminc-cmu
Copy link
Collaborator

Hi can you resolve the git conflict? I'll test this bug fix.
Thank you!

@gltanaka
Copy link
Contributor

target 1/20

@gltanaka
Copy link
Contributor

@beknobloch It looks like there are still conflicts

@jiaminc-cmu
Copy link
Collaborator

Hi. We refactored agentic_fix.py just yesterday to use the agentic_common.py module so seems like there is git conflict again. I can help with resolving git conflict and testing if you can kindly note why agentic fix was updated and what changes we're expected to keep. Thanks

@beknobloch
Copy link
Contributor Author

Hi! The change to agentic_fix.py was a tentative change I made while debugging sync - it's unconnected with this pull request, so I accepted your refactors and removed the changes I made in my commit. Sorry for the confusion there.

@jiaminc-cmu
Copy link
Collaborator

Hi I am getting a compilation error because there was a missing import. Would you fix it or would you mind if I pushed the fix?
⏺ Confirmed:

The patch for sync_orchestration.py only shows:

  • Import added: from .sync_tui import maybe_steer_operation
  • Usage: steer_timeout: float = DEFAULT_STEER_TIMEOUT_S

Missing: from .sync_tui import DEFAULT_STEER_TIMEOUT_S

This would cause exactly the NameError you saw. The fix I applied to your branch adds the missing import:

from .sync_tui import maybe_steer_operation, DEFAULT_STEER_TIMEOUT_S

You may want to comment on PR #267 about this bug, or the PR author needs to fix it before it can be merged.

To test your fixed local version, reinstall from the local directory:

pip install -e .

@beknobloch
Copy link
Contributor Author

Good catch, I had made an env variable so I didn't notice this bug. You can go ahead and push the fix. Thanks so much!

jiaminc-cmu added a commit that referenced this pull request Jan 24, 2026
The original PR #267 was missing this import, causing a NameError
when the module is loaded.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
jiaminc-cmu added a commit to beknobloch/pdd that referenced this pull request Jan 24, 2026
The original PR promptdriven#267 was missing this import, causing a NameError
when the module is loaded.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@jiaminc-cmu
Copy link
Collaborator

======================== short test summary info =========================
FAILED tests/test_sync_main.py::test_sync_dry_run_mode - AssertionError: 'sync_orchestration' does not contain all of (call('', ('log_test',), {'language': 'python', 'prompts_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None}), call('', ('log_test',), {'language': 'typescript', 'prompts_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None})) in its call list, found [call('', ('log_test',), {'language': 'python', 'prompts_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None, 'no_steer': False, 'steer_timeout': 8.0}), call('', ('log_test',), {'language': 'typescript', 'prompts_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/dx/6jpz4l8545179pngyxzmkhx80000gn/T/pytest-of-caijiamin/pytest-1582/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None, 'no_steer': False, 'steer_timeout': 8.0})] instead
FAILED tests/test_sync_orchestration.py::test_stopiteration_handling - assert False is True
========= 2 failed, 2856 passed, 9 skipped in 1045.64s (0:17:25) =========

@jiaminc-cmu
Copy link
Collaborator

jiaminc-cmu commented Jan 25, 2026

I pushed the fix for missing import. However, I am getting unit tests errors. Does this PR pass all unit tests and regressions?

@gltanaka gltanaka requested a review from Copilot January 25, 2026 20:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

tests/test_sync_tui.py:1

  • Use Any from typing instead of lowercase any for type hints.
import pytest

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

beknobloch and others added 12 commits January 26, 2026 16:07
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The original PR promptdriven#267 was missing this import, causing a NameError
when the module is loaded.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Add pdd/prompts/*.prompt to .gitignore (local convenience files)
- Add temporary files to .gitignore (error_log.txt, *.backup)
- Fix duplicate --local flag handling in regression.sh
- Add max_output_tokens field to ModelInfo in prompts.py
@beknobloch
Copy link
Contributor Author

I pushed the fix for missing import. However, I am getting unit tests errors. Does this PR pass all unit tests and regressions?

I just fixed the remaining issues caused by the merge conflict resolutions, did a full rebase, and reran unit tests and regression tests, and all pass. These issues should be resolved and the PR good to merge.

@gltanaka
Copy link
Contributor

Somehow I am getting conflicts: ```=========================== short test summary info ============================
FAILED tests/test_agentic_fix.py::test_run_agentic_fix_success_via_run_agentic_task
FAILED tests/test_commands_modify.py::test_cli_change_command_csv_validation
FAILED tests/test_agentic_fix.py::test_run_agentic_fix_handles_no_keys - asse...
FAILED tests/test_agentic_fix.py::test_run_agentic_fix_real_call_when_available[anthropic-ANTHROPIC_API_KEY-claude]
FAILED tests/test_agentic_fix.py::test_run_agentic_fix_real_call_when_available[google-GOOGLE_API_KEY-gemini]
FAILED tests/test_agentic_fix.py::test_run_agentic_fix_real_call_when_available[openai-OPENAI_API_KEY-codex]
FAILED tests/test_agentic_fix.py::TestCwdHandling::test_run_agentic_fix_respects_cwd_parameter
FAILED tests/test_agentic_fix.py::TestCwdHandling::test_run_agentic_fix_resolves_paths_against_cwd
FAILED tests/test_agentic_fix.py::TestMtimeChangeDetection::test_detects_new_files
FAILED tests/test_agentic_fix.py::TestMtimeChangeDetection::test_detects_modified_files
FAILED tests/test_agentic_fix.py::TestMtimeChangeDetection::test_detects_deleted_files
FAILED tests/test_fix_code_loop.py::TestCumulativeCostDisplay::test_total_cost_display_includes_prior_cost
FAILED tests/test_unfinished_prompt.py::test_unfinished_prompt_marks_mid_block_tail_without_dangling_as_finished
FAILED tests/test_e2e_issue_364_cumulative_cost.py::TestCumulativeCostDisplayE2E::test_fix_code_loop_displays_cumulative_cost
FAILED tests/test_e2e_issue_364_cumulative_cost.py::TestSyncOrchestrationCostAccumulation::test_sync_accumulated_cost_passed_to_fix_loops
================= 15 failed, 2949 passed in 339.05s (0:05:39) ==================
<<<PYTHON-EXEC-OUTPUT
Finished running tests!

@gltanaka
Copy link
Contributor

target 1/28

beknobloch and others added 3 commits January 28, 2026 21:45
…dividual path/color references instead of dictionaries. Update _detect_mtime_changes function in agentic_fix.py to handle new, modified, and deleted files more effectively. Introduce run_agentic_task wrapper for better testability. Adjust fix_code_loop to incorporate prior costs. Enhance error handling in SyncLock class for improved robustness.
@gltanaka
Copy link
Contributor

We tried to merge it in, but still got these failures on the unit tests:

`============================= test session starts ==============================
platform darwin -- Python 3.12.11, pytest-8.3.5, pluggy-1.6.0
rootdir: /Users/gregtanaka/Documents/pdd_cloud/pdd
configfile: pytest.ini
plugins: cov-5.0.0, anyio-4.12.0, asyncio-1.1.0, xdist-3.8.0, testmon-2.1.3, mock-3.14.1, langsmith-0.3.45
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function
created: 16/16 workers
16 workers [3015 items]

........................................................................ [ 2%]
........................................................................ [ 4%]
........................................................................ [ 7%]
........................................................................ [ 9%]
........................................................................ [ 11%]
........................................................................ [ 14%]
........................................................................ [ 16%]
........................................................................ [ 19%]
........................................................................ [ 21%]
........................................................................ [ 23%]
........................................................................ [ 26%]
........................................................................ [ 28%]
........................................................................ [ 31%]
........................................................................ [ 33%]
........................................................................ [ 35%]
........................................................................ [ 38%]
........................................................................ [ 40%]
........................................................................ [ 42%]
........................................................................ [ 45%]
........................................................................ [ 47%]
........................................................................ [ 50%]
........................................................................ [ 52%]
........................................................................ [ 54%]
........................................................................ [ 57%]
........................................................................ [ 59%]
........................................................................ [ 62%]
........................................................................ [ 64%]
................./opt/homebrew/Caskroom/miniforge/base/envs/pdd/lib/python3.12/site-packages/pydantic/main.py:463: UserWarning: Pydantic serializer warnings:
PydanticSerializationUnexpectedValue(Expected 10 fields but got 5: Expected Message - serialized value may not be as expected [input_value=Message(content='{"update...er_specific_fields=None), input_type=Message])
PydanticSerializationUnexpectedValue(Expected StreamingChoices - serialized value may not be as expected [input_value=Choices(finish_reason='st...r_specific_fields=None)), input_type=Choices])
return self.pydantic_serializer.to_python(
....................................................... [ 66%]
........................................................................ [ 69%]
........................................................................ [ 71%]
........................................................................ [ 74%]
................................................................F....... [ 76%]
........................................................................ [ 78%]
..........................................F............................. [ 81%]
........................................................................ [ 83%]
........................................................................ [ 85%]
........................................................................ [ 88%]
........................................................................ [ 90%]
........................................................................ [ 93%]
........................................................................ [ 95%]
........................................................................ [ 97%]
............................................................... [100%]
=================================== FAILURES ===================================
____________________________ test_sync_dry_run_mode ____________________________
[gw4] darwin -- Python 3.12.11 /opt/homebrew/Caskroom/miniforge/base/envs/pdd/bin/python

self =
calls = [call(basename='log_test', language='python', prompts_dir='/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/py...dry_run_mode0/test_project/tests', dry_run=True, verbose=True, quiet=False, context_override=None, agentic_mode=False)]
any_order = True

def assert_has_calls(self, calls, any_order=False):
    """assert the mock has been called with the specified calls.
    The `mock_calls` list is checked for the calls.

    If `any_order` is False (the default) then the calls must be
    sequential. There can be extra calls before or after the
    specified calls.

    If `any_order` is True then the calls can be in any order, but
    they must all appear in `mock_calls`."""
    expected = [self._call_matcher(c) for c in calls]
    cause = next((e for e in expected if isinstance(e, Exception)), None)
    all_calls = _CallList(self._call_matcher(c) for c in self.mock_calls)
    if not any_order:
        if expected not in all_calls:
            if cause is None:
                problem = 'Calls not found.'
            else:
                problem = ('Error processing expected calls.\n'
                           'Errors: {}').format(
                               [e if isinstance(e, Exception) else None
                                for e in expected])
            raise AssertionError(
                f'{problem}\n'
                f'Expected: {_CallList(calls)}'
                f'{self._calls_repr(prefix="  Actual").rstrip(".")}'
            ) from cause
        return

    all_calls = list(all_calls)

    not_found = []
    for kall in expected:
        try:
            all_calls.remove(kall)
        except ValueError:
            not_found.append(kall)
    if not_found:
      raise AssertionError(
            '%r does not contain all of %r in its call list, '
            'found %r instead' % (self._mock_name or 'mock',
                                  tuple(not_found), all_calls)
        ) from cause

E AssertionError: 'sync_orchestration' does not contain all of (call('', ('log_test',), {'language': 'python', 'prompts_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None, 'agentic_mode': False}), call('', ('log_test',), {'language': 'typescript', 'prompts_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None, 'agentic_mode': False})) in its call list, found [call('', ('log_test',), {'language': 'python', 'prompts_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None, 'agentic_mode': False, 'no_steer': False, 'steer_timeout': 8.0}), call('', ('log_test',), {'language': 'typescript', 'prompts_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None, 'agentic_mode': False, 'no_steer': False, 'steer_timeout': 8.0})] instead

/opt/homebrew/Caskroom/miniforge/base/envs/pdd/lib/python3.12/unittest/mock.py:1002: AssertionError

During handling of the above exception, another exception occurred:

mock_project_dir = PosixPath('/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project')
mock_construct_paths = <function construct_paths at 0x10a16cae0>
mock_sync_orchestration = <function sync_orchestration at 0x10a175ee0>

def test_sync_dry_run_mode(mock_project_dir, mock_construct_paths, mock_sync_orchestration):
    """Tests that --dry-run mode calls sync_orchestration correctly."""
    (mock_project_dir / "prompts" / "log_test_python.prompt").touch()
    (mock_project_dir / "prompts" / "log_test_typescript.prompt").touch()

    ctx = create_mock_context({"verbose": True})
    results, total_cost, model = sync_main(
        ctx, "log_test", max_attempts=3, budget=10.0, skip_verify=False, skip_tests=False, target_coverage=90.0, dry_run=True
    )

    assert mock_sync_orchestration.call_count == 2
    calls = [
        call(
            basename='log_test',
            language='python',
            prompts_dir=str(mock_project_dir / 'prompts'),
            code_dir=str(mock_project_dir / 'src'),
            examples_dir=str(mock_project_dir / 'examples'),
            tests_dir=str(mock_project_dir / 'tests'),
            dry_run=True,
            verbose=True,
            quiet=False,
            context_override=None,
            agentic_mode=False,
        ),
        call(
            basename='log_test',
            language='typescript',
            prompts_dir=str(mock_project_dir / 'prompts'),
            code_dir=str(mock_project_dir / 'src'),
            examples_dir=str(mock_project_dir / 'examples'),
            tests_dir=str(mock_project_dir / 'tests'),
            dry_run=True,
            verbose=True,
            quiet=False,
            context_override=None,
            agentic_mode=False,
        ),
    ]
  mock_sync_orchestration.assert_has_calls(calls, any_order=True)

/Users/gregtanaka/Documents/pdd_cloud/pdd/tests/test_sync_main.py:444:


args = ([call(basename='log_test', language='python', prompts_dir='/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/p...y_run_mode0/test_project/tests', dry_run=True, verbose=True, quiet=False, context_override=None, agentic_mode=False)],)
kwargs = {'any_order': True}

def assert_has_calls(*args, **kwargs):
  return mock.assert_has_calls(*args, **kwargs)

E AssertionError: 'sync_orchestration' does not contain all of (call('', ('log_test',), {'language': 'python', 'prompts_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None, 'agentic_mode': False}), call('', ('log_test',), {'language': 'typescript', 'prompts_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None, 'agentic_mode': False})) in its call list, found [call('', ('log_test',), {'language': 'python', 'prompts_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None, 'agentic_mode': False, 'no_steer': False, 'steer_timeout': 8.0}), call('', ('log_test',), {'language': 'typescript', 'prompts_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/prompts', 'code_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/src', 'examples_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/examples', 'tests_dir': '/private/var/folders/zz/zf42knl51xd_k80fv62n_wfr0000gn/T/pytest-of-gregtanaka/pytest-44/popen-gw4/test_sync_dry_run_mode0/test_project/tests', 'dry_run': True, 'verbose': True, 'quiet': False, 'context_override': None, 'agentic_mode': False, 'no_steer': False, 'steer_timeout': 8.0})] instead

/opt/homebrew/Caskroom/miniforge/base/envs/pdd/lib/python3.12/unittest/mock.py:222: AssertionError
----------------------------- Captured stdout call -----------------------------
╭────────── PDD Sync Dry Run ───────────╮
│ Displaying sync analysis for log_test │
╰───────────────────────────────────────╯

--- Log for language: python ---

--- Log for language: typescript ---
_________________________ test_stopiteration_handling __________________________
[gw8] darwin -- Python 3.12.11 /opt/homebrew/Caskroom/miniforge/base/envs/pdd/bin/python

orchestration_fixture = {'SyncApp': , 'SyncLock': , '_dis...nc_log' id='4630175552'>, '_save_fingerprint_atomic': , ...}

def test_stopiteration_handling(orchestration_fixture):
    """
    Verify that StopIteration from sync_determine_operation is handled gracefully.
    """
    mocks = orchestration_fixture
    mock_determine = mocks['sync_determine_operation']

    # Configure mock to raise StopIteration (simulating exhausted iterator in tests)
    mock_determine.side_effect = StopIteration("Decision sequence exhausted")

    result = sync_orchestration(basename="calculator", language="python")

    # Verify the result indicates completion (treated as all_synced)
  assert result['success'] is True

E assert False is True

/Users/gregtanaka/Documents/pdd_cloud/pdd/tests/test_sync_orchestration.py:2379: AssertionError
----------------------------- Captured stdout call -----------------------------
╭─────────────────────────────────╮
│ │
│ +xxxxxxxxxxxxxxx+ │
│ xxxxxxxxxxxxxxxxxxxxx+ │
│ xxx +xx+ │
│ xxx x+ xx+ │
│ xxx x+ xxx │
│ xxx x+ xx+ │
│ xxx x+ xx+ │
│ xxx x+ xxx │
│ xxx +xx+ │
│ xxx +xxxxxxxxxxx+ │
│ xxx +xx+ │
│ xxx +xx+ │
│ xxx+xx+ │
│ xxxx+ │
│ xx+ │
│ │
╰─────────────────────────────────╯

----------------------------- Captured stderr call -----------------------------
Traceback (most recent call last):
File "/Users/gregtanaka/Documents/pdd_cloud/pdd/pdd/sync_orchestration.py", line 1103, in sync_worker_logic
decision = sync_determine_operation(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Caskroom/miniforge/base/envs/pdd/lib/python3.12/unittest/mock.py", line 1139, in call
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Caskroom/miniforge/base/envs/pdd/lib/python3.12/unittest/mock.py", line 1143, in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Caskroom/miniforge/base/envs/pdd/lib/python3.12/unittest/mock.py", line 1198, in _execute_mock_call
raise effect
StopIteration: Decision sequence exhausted

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/Users/gregtanaka/Documents/pdd_cloud/pdd/pdd/sync_orchestration.py", line 1126, in sync_worker_logic
log_sync_event(
^^^^^^^^^^^^^^
NameError: name 'log_sync_event' is not defined
=========================== short test summary info ============================
FAILED tests/test_sync_main.py::test_sync_dry_run_mode - AssertionError: 'syn...
FAILED tests/test_sync_orchestration.py::test_stopiteration_handling - assert...
================= 2 failed, 3013 passed in 3647.18s (1:00:47) ==================
<<<PYTHON-EXEC-OUTPUT
Finished running tests!`

@gltanaka
Copy link
Contributor

Also, somehow the option to select what command should come next doesn't pop up anymore. I noticed that while the box resizes, the animation doesn't resize dynamically as it used to?

@beknobloch
Copy link
Contributor Author

Huh, those unit tests don't fail for me on my end:

(pdd) benjaminknobloch@Benjamins-MacBook-Pro pdd % pytest tests/test_sync_orchestration.py 
=================================================================================================================== test session starts ===================================================================================================================
platform darwin -- Python 3.12.12, pytest-8.3.5, pluggy-1.5.0
rootdir: /Users/benjaminknobloch/GitHub_Repositories/pdd
configfile: pytest.ini
plugins: mock-3.15.1, cov-5.0.0, anyio-4.12.0, xdist-3.8.0, asyncio-1.0.0, testmon-2.2.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function
collected 94 items                                                                                                                                                                                                                                        

tests/test_sync_orchestration.py ..............................................................................................                                                                                                                     [100%]

=================================================================================================================== 94 passed in 53.21s ===================================================================================================================
(pdd) benjaminknobloch@Benjamins-MacBook-Pro pdd % pytest tests/test_sync_main.py         
=================================================================================================================== test session starts ===================================================================================================================
platform darwin -- Python 3.12.12, pytest-8.3.5, pluggy-1.5.0
rootdir: /Users/benjaminknobloch/GitHub_Repositories/pdd
configfile: pytest.ini
plugins: mock-3.15.1, cov-5.0.0, anyio-4.12.0, xdist-3.8.0, asyncio-1.0.0, testmon-2.2.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function
collected 32 items                                                                                                                                                                                                                                        

tests/test_sync_main.py ................................                                                                                                                                                                                            [100%]

=================================================================================================================== 32 passed in 1.52s ====================================================================================================================
(pdd) benjaminknobloch@Benjamins-MacBook-Pro pdd % git checkout steering-sync 
Switched to branch 'steering-sync'
Your branch is up to date with 'origin/steering-sync'.
(pdd) benjaminknobloch@Benjamins-MacBook-Pro pdd % pytest tests/test_sync_orchestration.py
=================================================================================================================== test session starts ===================================================================================================================
platform darwin -- Python 3.12.12, pytest-8.3.5, pluggy-1.5.0
rootdir: /Users/benjaminknobloch/GitHub_Repositories/pdd
configfile: pytest.ini
plugins: mock-3.15.1, cov-5.0.0, anyio-4.12.0, xdist-3.8.0, asyncio-1.0.0, testmon-2.2.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function
collected 95 items                                                                                                                                                                                                                                        

tests/test_sync_orchestration.py ............................................................................................... [100%]

========================== 95 passed in 54.10s ===========================
(pdd) benjaminknobloch@Benjamins-MacBook-Pro pdd % pytest tests/test_sync_main.py         
=================================================================================================================== test session starts ===================================================================================================================
platform darwin -- Python 3.12.12, pytest-8.3.5, pluggy-1.5.0
rootdir: /Users/benjaminknobloch/GitHub_Repositories/pdd
configfile: pytest.ini
plugins: mock-3.15.1, cov-5.0.0, anyio-4.12.0, xdist-3.8.0, asyncio-1.0.0, testmon-2.2.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function
collected 32 items                                                                                                                                                                                                                                        

tests/test_sync_main.py ................................                                                                                                                                                                                            [100%]

=================================================================================================================== 32 passed in 1.59s ====================================================================================================================

I don't have any local or unpushed changes, either. Do you know what might be happening? I'll do a manual test now to see if the sizing has also reverted like you reported.

@gltanaka
Copy link
Contributor

gltanaka commented Feb 3, 2026

@beknobloch I investigated why the tests were failing on our end but passing for you, and found the root cause.

Bug Found and Fixed

In pdd/sync_orchestration.py, the StopIteration exception handler was calling log_sync_event() which doesn't exist - the correct function is log_event(). I just pushed a fix to your branch: 4714378

Before (broken):

log_sync_event(
    basename,
    language,
    "decision_exhausted",
    {"note": "StopIteration from sync_determine_operation"},
)

After (fixed):

log_event(
    basename,
    language,
    "decision_exhausted",
    {"note": "StopIteration from sync_determine_operation"},
    invocation_mode="sync",
)

Why Your Tests Passed

This is puzzling since the bug existed on your remote branch (public/steering-sync). Possible explanations:

  1. Local uncommitted fix that wasn't pushed
  2. Test caching (pytest-testmon or similar) skipping the affected test
  3. Your test output shows 95 tests passed, but the branch has 101 test functions - some tests may not have been running

Additional Context for Merging

When merging to main, there are also conflicts to resolve because main now has agentic_mode parameter that your branch doesn't have. Both features need to be preserved:

  • agentic_mode (from main)
  • no_steer and steer_timeout (from your PR)

I've already done this merge work on branch fix-pr-267-merge - let me know if you'd like me to update your PR with the fully merged version, or if you prefer to handle the merge yourself.

@gltanaka
Copy link
Contributor

gltanaka commented Feb 3, 2026

71D0ACD6-E32E-4F2D-B251-0589F52781B5_1_102_o

@gltanaka
Copy link
Contributor

gltanaka commented Feb 3, 2026

here is a screen recording: https://photos.app.goo.gl/1bajmEcNpnZadQGJ7

somehow the screen to chose different commands doesn't come up anymore and animation doesn't seem to resize either

@beknobloch
Copy link
Contributor Author

The resizing still looks good on my end. To clarify the PR description, vertical resizing works correctly and I don't get the visual distortion seen on main, while I disabled horizontal resizing because it interferes with Textual's implementation of the animations, and attempting to implement it causes different kinds of visual glitches. I could switch to implement it if that's, but based on what I tried it might take more fundamental changes to how Textual is used for the visuals

@gltanaka
Copy link
Contributor

gltanaka commented Feb 3, 2026

target 2/4

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.

Terminal UI break when terminal is resized during pdd sync Make sync steerable

3 participants