-
Notifications
You must be signed in to change notification settings - Fork 5
fix: conservative success detection in GoldenPathRunner (#374) #381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| """Tests that `cf work start --execute` returns non-zero exit codes for BLOCKED/FAILED. | ||
|
|
||
| Issue #374: CLI was returning exit code 0 even when the agent state was | ||
| BLOCKED or FAILED, causing false positives in test automation. | ||
| """ | ||
|
|
||
| from dataclasses import dataclass, field | ||
| from unittest.mock import patch | ||
|
|
||
| import pytest | ||
| from typer.testing import CliRunner | ||
|
|
||
| from codeframe.cli.app import app | ||
| from codeframe.core.agent import AgentStatus | ||
| from codeframe.core import tasks | ||
| from codeframe.core.state_machine import TaskStatus | ||
| from codeframe.core.workspace import create_or_load_workspace | ||
|
|
||
| pytestmark = pytest.mark.v2 | ||
|
|
||
| runner = CliRunner() | ||
|
|
||
|
|
||
| @dataclass | ||
| class _FakeAgentState: | ||
| status: AgentStatus | ||
| blocker: object = None | ||
| step_results: list = field(default_factory=list) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def workspace_with_ready_task(tmp_path, monkeypatch): | ||
| """Workspace with one READY task, API key set.""" | ||
| repo = tmp_path / "repo" | ||
| repo.mkdir() | ||
| monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-test-fake") | ||
|
|
||
| ws = create_or_load_workspace(repo) | ||
| task = tasks.create(ws, title="Test task", description="A test task", | ||
| status=TaskStatus.READY) | ||
|
|
||
| return repo, task.id[:8] | ||
|
|
||
|
|
||
| class TestWorkStartExitCodes: | ||
| """Verify CLI exit codes match agent execution outcomes.""" | ||
|
|
||
| def test_completed_returns_exit_zero(self, workspace_with_ready_task): | ||
| repo, tid = workspace_with_ready_task | ||
| fake_state = _FakeAgentState(status=AgentStatus.COMPLETED) | ||
|
|
||
| with patch("codeframe.core.runtime.execute_agent", return_value=fake_state): | ||
| result = runner.invoke( | ||
| app, ["work", "start", tid, "--execute", "-w", str(repo)] | ||
| ) | ||
|
|
||
| assert result.exit_code == 0, f"Expected 0 for COMPLETED: {result.output}" | ||
| assert "completed successfully" in result.output.lower() | ||
|
|
||
| def test_failed_returns_exit_one(self, workspace_with_ready_task): | ||
| repo, tid = workspace_with_ready_task | ||
| fake_state = _FakeAgentState(status=AgentStatus.FAILED) | ||
|
|
||
| with patch("codeframe.core.runtime.execute_agent", return_value=fake_state): | ||
| result = runner.invoke( | ||
| app, ["work", "start", tid, "--execute", "-w", str(repo)] | ||
| ) | ||
|
|
||
| assert result.exit_code == 1, f"Expected 1 for FAILED: {result.output}" | ||
| assert "failed" in result.output.lower() | ||
|
|
||
| def test_blocked_returns_exit_one(self, workspace_with_ready_task): | ||
| repo, tid = workspace_with_ready_task | ||
| fake_state = _FakeAgentState(status=AgentStatus.BLOCKED) | ||
|
|
||
| with patch("codeframe.core.runtime.execute_agent", return_value=fake_state): | ||
| result = runner.invoke( | ||
| app, ["work", "start", tid, "--execute", "-w", str(repo)] | ||
| ) | ||
|
|
||
| assert result.exit_code == 1, f"Expected 1 for BLOCKED: {result.output}" | ||
| assert "blocked" in result.output.lower() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| """Unit tests for GoldenPathRunner._detect_success(). | ||
|
|
||
| Validates that the success detection logic uses conservative defaults: | ||
| - Explicit success patterns → True | ||
| - Explicit failure patterns → False | ||
| - No patterns matched → False (not True!) | ||
| - Non-zero exit code → always False | ||
| """ | ||
|
|
||
| import pytest | ||
|
|
||
| from tests.e2e.cli.golden_path_runner import GoldenPathRunner | ||
|
|
||
| pytestmark = pytest.mark.v2 | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def runner(tmp_path): | ||
| """Create a GoldenPathRunner instance for testing.""" | ||
| return GoldenPathRunner(project_path=tmp_path, engine="react") | ||
|
|
||
|
|
||
| class TestDetectSuccess: | ||
| """Tests for _detect_success() conservative detection logic.""" | ||
|
|
||
| def test_explicit_success_pattern_returns_true(self, runner): | ||
| output = "Some output\nTask completed successfully!\nDone." | ||
| assert runner._detect_success(exit_code=0, output=output) is True | ||
|
|
||
| def test_explicit_failure_pattern_returns_false(self, runner): | ||
| output = "Task execution failed\nSome error occurred" | ||
| assert runner._detect_success(exit_code=0, output=output) is False | ||
|
|
||
| def test_api_key_missing_returns_false(self, runner): | ||
| output = "ANTHROPIC_API_KEY environment variable is required" | ||
| assert runner._detect_success(exit_code=0, output=output) is False | ||
|
|
||
| def test_error_pattern_returns_false(self, runner): | ||
| output = "Error: something went wrong" | ||
| assert runner._detect_success(exit_code=0, output=output) is False | ||
|
|
||
| def test_blocked_pattern_returns_false(self, runner): | ||
| output = "Task blocked - needs human input" | ||
| assert runner._detect_success(exit_code=0, output=output) is False | ||
|
|
||
| def test_empty_output_exit_zero_returns_false(self, runner): | ||
| """The core bug: empty output should NOT be treated as success.""" | ||
| assert runner._detect_success(exit_code=0, output="") is False | ||
|
|
||
| def test_no_patterns_exit_zero_returns_false(self, runner): | ||
| """Output with no matching patterns should be failure (conservative).""" | ||
| output = "Some random output that matches nothing" | ||
| assert runner._detect_success(exit_code=0, output=output) is False | ||
|
|
||
| def test_nonzero_exit_code_always_returns_false(self, runner): | ||
| output = "Task completed successfully!" | ||
| assert runner._detect_success(exit_code=1, output=output) is False | ||
|
|
||
| def test_nonzero_exit_code_with_no_output(self, runner): | ||
| assert runner._detect_success(exit_code=1, output="") is False | ||
|
|
||
| def test_mixed_success_and_failure_returns_false(self, runner): | ||
| """When both success and failure patterns present, failure wins.""" | ||
| output = "Task completed successfully!\nBut then Error: crash" | ||
| assert runner._detect_success(exit_code=0, output=output) is False | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.