Skip to content

Commit e0c3490

Browse files
devin-ai-integration[bot]erkinalp
andcommittedDec 20, 2024
Preserve output on command timeout
When a command times out, the system now preserves and returns any output that was collected before the timeout occurred. This improves the user experience by not losing potentially valuable partial output. Changes: - Add partial_output to CommandTimeoutError - Capture and return partial output in timeout scenarios - Update documentation to reflect new behavior Fixes SWE-agent#29 Co-Authored-By: Erkin Alp Güney <[email protected]>

File tree

2 files changed

+21
-5
lines changed

2 files changed

+21
-5
lines changed
 

Diff for: ‎src/swerex/exceptions.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,18 @@ def __init__(self, message: str, *, extra_info: dict[str, Any] = None):
2525
self.extra_info = extra_info
2626

2727

28-
class CommandTimeoutError(SwerexException, RuntimeError, TimeoutError): ...
28+
class CommandTimeoutError(SwerexException, RuntimeError, TimeoutError):
29+
"""Raised when a command times out, but includes any output collected before the timeout.
30+
31+
Attributes:
32+
partial_output (str): The output that was collected before the timeout occurred
33+
"""
34+
def __init__(self, message: str, partial_output: str = "", *, extra_info: dict[str, Any] = None):
35+
super().__init__(message)
36+
self.partial_output = partial_output
37+
if extra_info is None:
38+
extra_info = {}
39+
self.extra_info = extra_info
2940

3041

3142
class NoExitCodeError(SwerexException, RuntimeError): ...

Diff for: ‎src/swerex/runtime/local.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,12 @@ async def run(self, action: BashAction | BashInterruptAction) -> BashObservation
217217
218218
Raises:
219219
SessionNotInitializedError: If the shell is not initialized.
220-
CommandTimeoutError: If the command times out.
220+
CommandTimeoutError: If the command times out. The error will contain any output collected before the timeout.
221221
NonZeroExitCodeError: If the command has a non-zero exit code and `action.check` is True.
222222
NoExitCodeError: If we cannot get the exit code of the command.
223223
224224
Returns:
225-
BashObservation: The observation of the command.
225+
BashObservation: The observation of the command, including any partial output if a timeout occurred.
226226
"""
227227
if self.shell is None:
228228
msg = "shell not initialized"
@@ -231,7 +231,11 @@ async def run(self, action: BashAction | BashInterruptAction) -> BashObservation
231231
return await self.interrupt(action)
232232
if action.is_interactive_command or action.is_interactive_quit:
233233
return await self._run_interactive(action)
234-
r = await self._run_normal(action)
234+
try:
235+
r = await self._run_normal(action)
236+
except CommandTimeoutError as e:
237+
# Return partial output on timeout
238+
return BashObservation(output=e.partial_output, exit_code=None, failure_reason="timeout")
235239
if action.check == "raise" and r.exit_code != 0:
236240
msg = (
237241
f"Command {action.command!r} failed with exit code {r.exit_code}. " f"Here is the output:\n{r.output!r}"
@@ -309,8 +313,9 @@ async def _run_normal(self, action: BashAction) -> BashObservation:
309313
expect_index = self.shell.expect(expect_strings, timeout=action.timeout) # type: ignore
310314
matched_expect_string = expect_strings[expect_index]
311315
except pexpect.TIMEOUT as e:
316+
partial_output = _strip_control_chars(self.shell.before).strip() # type: ignore
312317
msg = f"timeout after {action.timeout} seconds while running command {action.command!r}"
313-
raise CommandTimeoutError(msg) from e
318+
raise CommandTimeoutError(msg, partial_output=partial_output) from e
314319
output: str = _strip_control_chars(self.shell.before).strip() # type: ignore
315320

316321
# Part 3: Get the exit code

0 commit comments

Comments
 (0)
Please sign in to comment.