From 55ca03ffd61e50af2510618a80836824c71b68d5 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 16:12:45 -0400 Subject: [PATCH 1/3] Map installer crashes to the owning repository source --- src/agent_comms/installer/launch.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/agent_comms/installer/launch.py b/src/agent_comms/installer/launch.py index 42a55a8894..fb8068363c 100644 --- a/src/agent_comms/installer/launch.py +++ b/src/agent_comms/installer/launch.py @@ -31,6 +31,7 @@ from agent_comms.installer.reporting import ( FinishedInstall, InstallOutcome, RootWriter, UnfinishedInstall, UnpostedWriter, ) +from agent_comms.installer.runtime_resolution import CorePackage from agent_comms.installer.probes.harnesses import HarnessesProbe from agent_comms.installer.upgrade import ( RehearsedUpgrade, @@ -40,6 +41,7 @@ upgrade, ) from agent_comms.mro_dispatch import MroDispatch, handles +from agent_comms.repo import Repo from agent_comms.storage_rules import StorageRule @@ -50,6 +52,7 @@ class InstallReport(MroDispatch): writer: RootWriter = field(default_factory=UnpostedWriter) outcome: InstallOutcome = field(default_factory=UnfinishedInstall, init=False) bridges: SessionBridgeResumes = field(default_factory=SessionBridgeResumes, init=False) + source: Repo = field(init=False, repr=False) def complete(self, result: RehearsedUpgrade) -> RehearsedUpgrade: self.outcome = FinishedInstall(result) @@ -65,14 +68,23 @@ def refused(self, error: UpgradeRefused) -> tuple[str, ...]: @handles(BaseException) def crashed(self, error: BaseException) -> tuple[str, ...]: - frame = extract_tb(error.__traceback__)[-1] + package = Path(agent_comms.__file__).resolve().parent + frame = next( + frame for frame in reversed(extract_tb(error.__traceback__)) + if Path(frame.filename).resolve().is_relative_to(package) + ) + module = ".".join( + Path(frame.filename).resolve().relative_to(package.parent).with_suffix("").parts + ) + location = self.source.module_file(module) return (f"Install failed: {type(error).__name__}: {error}", - f"{frame.filename}:{frame.lineno}") + f"{location}:{frame.lineno}") @contextmanager def running(self, tree: TargetTree) -> Iterator[None]: """Every writer runs installed artifacts from outside ``tree``, so the report follows the tree's cleanup and a cleanup failure is the install's failure.""" + self.source = Repo(checkout=tree.checkout, github=CorePackage.repository) try: with tree: yield From 944499c40d8d8a783b943f9cb86877d92f5c6372 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 16:14:21 -0400 Subject: [PATCH 2/3] Keep returned live results in failed install outcomes --- src/agent_comms/installer/launch.py | 12 ++++++--- src/agent_comms/installer/reporting.py | 35 +++++++++++++++++++++++++- src/agent_comms/installer/upgrade.py | 10 ++++++-- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/agent_comms/installer/launch.py b/src/agent_comms/installer/launch.py index fb8068363c..6d3543bea1 100644 --- a/src/agent_comms/installer/launch.py +++ b/src/agent_comms/installer/launch.py @@ -29,11 +29,13 @@ from agent_comms.installer.refusals import UpgradeRefused from agent_comms.installer.commands import InstallCommands from agent_comms.installer.reporting import ( - FinishedInstall, InstallOutcome, RootWriter, UnfinishedInstall, UnpostedWriter, + FailedInstall, FinishedInstall, InstallOutcome, ObservedInstall, RootWriter, + UnfinishedInstall, UnpostedWriter, ) from agent_comms.installer.runtime_resolution import CorePackage from agent_comms.installer.probes.harnesses import HarnessesProbe from agent_comms.installer.upgrade import ( + LiveRun, RehearsedUpgrade, Stack, TargetTree, @@ -54,6 +56,10 @@ class InstallReport(MroDispatch): bridges: SessionBridgeResumes = field(default_factory=SessionBridgeResumes, init=False) source: Repo = field(init=False, repr=False) + def record_live(self, result: LiveRun) -> LiveRun: + self.outcome = ObservedInstall(result) + return result + def complete(self, result: RehearsedUpgrade) -> RehearsedUpgrade: self.outcome = FinishedInstall(result) return result @@ -90,9 +96,9 @@ def running(self, tree: TargetTree) -> Iterator[None]: yield lines = self.outcome.lines() except BaseException as error: - failure = (*next(self.handlers_for(error))(error), *self.bridges.lines()) + self.outcome = FailedInstall(self.outcome, next(self.handlers_for(error))(error)) try: - self.writer.post(failure) + self.writer.post((*self.outcome.lines(), *self.bridges.lines())) except BaseException as publication_error: error.add_note(f"Install failure report also failed: {publication_error}") print(f"install: failure report also failed: {publication_error}", file=sys.stderr) diff --git a/src/agent_comms/installer/reporting.py b/src/agent_comms/installer/reporting.py index cbc130cb75..bd3d9497c9 100644 --- a/src/agent_comms/installer/reporting.py +++ b/src/agent_comms/installer/reporting.py @@ -9,18 +9,34 @@ from agent_comms.installer.probes.report import InstallReportProbe if TYPE_CHECKING: - from agent_comms.installer.upgrade import RehearsedUpgrade, Stack + from agent_comms.installer.upgrade import LiveRun, RehearsedUpgrade, Stack class InstallOutcome(ABC): @abstractmethod def lines(self) -> tuple[str, ...]: ... + @abstractmethod + def live_lines(self) -> tuple[str, ...]: ... + class UnfinishedInstall(InstallOutcome): def lines(self) -> tuple[str, ...]: raise RuntimeError("Install exited without producing its result") + def live_lines(self) -> tuple[str, ...]: + return () + + +@dataclass(frozen=True) +class ObservedInstall(UnfinishedInstall): + """A live result returned before harness registration or cleanup can fail.""" + + live: LiveRun + + def live_lines(self) -> tuple[str, ...]: + return self.live.lines() + @dataclass(frozen=True) class FinishedInstall(InstallOutcome): @@ -29,6 +45,23 @@ class FinishedInstall(InstallOutcome): def lines(self) -> tuple[str, ...]: return (self.result.finished, *self.result.lines()) + def live_lines(self) -> tuple[str, ...]: + return self.result.live_lines() + + +@dataclass(frozen=True) +class FailedInstall(InstallOutcome): + """Failure retains the result already observed, without replacing its error.""" + + previous: InstallOutcome + failure: tuple[str, ...] + + def lines(self) -> tuple[str, ...]: + return (*self.failure, *self.live_lines()) + + def live_lines(self) -> tuple[str, ...]: + return self.previous.live_lines() + class RootWriter(ABC): @abstractmethod diff --git a/src/agent_comms/installer/upgrade.py b/src/agent_comms/installer/upgrade.py index c1b583fb51..ce1a7af955 100644 --- a/src/agent_comms/installer/upgrade.py +++ b/src/agent_comms/installer/upgrade.py @@ -339,6 +339,9 @@ class RehearsedUpgrade(Upgrade): finished: ClassVar[str] = "Install rehearsed" + def live_lines(self) -> tuple[str, ...]: + return () + def lines(self) -> tuple[str, ...]: slowest = sorted(self.commands, key=lambda timed: timed.seconds, reverse=True)[:5] return (*super().lines(), @@ -359,8 +362,11 @@ class InstalledUpgrade(RehearsedUpgrade, Extendable): finished: ClassVar[str] = "Install finished" """The headline of the post that reports this upgrade.""" + def live_lines(self) -> tuple[str, ...]: + return self.live.lines() + def lines(self) -> tuple[str, ...]: - return (*super().lines(), *self.live.lines(), *self.processes.lines(), + return (*super().lines(), *self.live_lines(), *self.processes.lines(), *(line for outcome in self.bridge_resumes for line in outcome.lines())) @@ -813,7 +819,7 @@ def install(self, rehearsal: Rehearsed, bridges: SessionBridgeResumes, try: for bridge in Serve.running(self.installed.home): bridges.pause(bridge, resources) - return self.live(rehearsal, report) + return report.record_live(self.live(rehearsal, report)) finally: # A later pause may fail; resume captured peers before closing their sockets. bridges.resume() From 93b87c6290e6c47c010d666ecf6f3d2dcdec3329 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 16:28:12 -0400 Subject: [PATCH 3/3] Use the declared target-tree path for crash source mapping --- src/agent_comms/installer/launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agent_comms/installer/launch.py b/src/agent_comms/installer/launch.py index 6d3543bea1..9555c51e6f 100644 --- a/src/agent_comms/installer/launch.py +++ b/src/agent_comms/installer/launch.py @@ -90,7 +90,7 @@ def crashed(self, error: BaseException) -> tuple[str, ...]: def running(self, tree: TargetTree) -> Iterator[None]: """Every writer runs installed artifacts from outside ``tree``, so the report follows the tree's cleanup and a cleanup failure is the install's failure.""" - self.source = Repo(checkout=tree.checkout, github=CorePackage.repository) + self.source = Repo(checkout=tree.path, github=CorePackage.repository) try: with tree: yield