Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions sdk/python/agentfield/harness/_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,15 +294,18 @@ async def run(
# project_dir, or cwd when project_dir is unset. Besides keeping the file
# inside the agent root, this prevents concurrent runs sharing one cwd
# from overwriting or deleting each other's fixed output filename.
resolved_project_dir = options.get("project_dir")
if isinstance(resolved_project_dir, str) and resolved_project_dir:
base_dir = resolved_project_dir
else:
base_dir = resolved_cwd
os.makedirs(base_dir, exist_ok=True)
temp_output_dir: Optional[str] = tempfile.mkdtemp(
prefix=".agentfield-out-", dir=base_dir
)
# A schema-free run never writes or reads that file, so it must not need
# a writable root either: allocating one unconditionally aborted
# text-only permission_mode="plan" dispatches before the provider ran.
temp_output_dir: Optional[str] = None
if schema is not None:
resolved_project_dir = options.get("project_dir")
if isinstance(resolved_project_dir, str) and resolved_project_dir:
base_dir = resolved_project_dir
else:
base_dir = resolved_cwd
os.makedirs(base_dir, exist_ok=True)
temp_output_dir = tempfile.mkdtemp(prefix=".agentfield-out-", dir=base_dir)
output_dir = temp_output_dir

# schema_mode selects how the agent is asked to produce the output:
Expand Down
33 changes: 33 additions & 0 deletions sdk/python/tests/test_harness_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,39 @@ async def test_run_without_schema_returns_plain_harness_result(tmp_path):
assert result.session_id == "sess-1"


@pytest.mark.asyncio
async def test_run_without_schema_does_not_require_writable_project_dir(tmp_path):
"""A schema-free run must reach the provider without allocating artifacts.

The per-run ``.agentfield-out-*`` directory only exists to hold the schema
output file (#684, #891). Allocating it unconditionally aborted a text-only
``permission_mode="plan"`` run during setup, before the provider was ever
dispatched, whenever the project root could not be written to.
"""
blocker = tmp_path / "blocker.txt"
blocker.write_text("not a directory", encoding="utf-8")
# Cannot be created on any platform: its parent is a regular file.
unwritable_root = blocker / "project"

provider = MockProvider([RawResult(result="plan text")])
runner = HarnessRunner()

with patch("agentfield.harness._runner.build_provider", return_value=provider):
result = await runner.run(
"hello",
provider="codex",
permission_mode="plan",
cwd=str(tmp_path),
project_dir=str(unwritable_root),
)

assert provider.call_count == 1, "schema-free run must dispatch the provider"
assert result.is_error is False
assert result.result == "plan text"
assert result.parsed is None
assert list(tmp_path.glob(".agentfield-out-*")) == []


@pytest.mark.asyncio
async def test_run_with_schema_injects_prompt_suffix_and_parses_output(tmp_path):
provider = FileWritingProvider(json.dumps({"name": "ok", "count": 1}))
Expand Down
Loading