Files
zeekay 1b367e2b62 chore: rebase the tree onto upstream master
The tree was a debranded snapshot of upstream taken around 2026-02-20, and
upstream has moved about five months since. This replaces it with upstream
master, mechanically re-derived: bin/debrand does the rename, and the
pipeline and squashed migrations are restored from the shipped tree.

Not a merge. The previous import replaced the tree wholesale in a single
commit, so there is no shared content history to merge against. Reproducing
the rename on the new tree is what actually converges.

What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints,
compose files, LLM.md, and insights/migrations. The squashed initial
migration is load-bearing — the live database records it by name, so
upstream's ~918 individual migrations cannot come back.

Nine products are dropped as unreferenced. Twenty-one more that we do not run
are kept anyway because the core app still imports them; unpicking those is a
refactor and does not belong in a rebase.

This base does not build yet, and the reasons are not in the rename:
upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against
our 4.2, and the dependency manifests still name upstream packages that we
republish under our own names. Those are decisions, not renames.
2026-08-01 15:44:40 -07:00

230 lines
7.1 KiB
Python

from datetime import timedelta
from typing import Any, Optional
from temporalio.exceptions import ApplicationError
from insights.exceptions_capture import capture_exception
class ProcessTaskError(ApplicationError):
def __init__(
self, message: str, context: dict[str, Any], cause: Optional[BaseException], capture: bool = True, **kwargs
):
self.context = context or {}
if "team" not in self.context:
self.context["team"] = "array"
# `capture=False` skips error-tracking capture for expected, recoverable failures
# (e.g. transient infra timeouts that Temporal retries) so they don't create noisy issues.
if cause is not None and capture:
capture_exception(cause, self.context)
# Retry policies match non_retryable_error_types against this type; the SDK omits it unless set.
kwargs.setdefault("type", type(self).__name__)
super().__init__(message, self.context, **kwargs)
class ProcessTaskFatalError(ProcessTaskError):
"""Fatal errors that should not be retried."""
def __init__(self, message: str, context: dict[str, Any], cause: Exception, **kwargs):
super().__init__(message, context, cause, non_retryable=True, **kwargs)
class ProcessTaskTransientError(ProcessTaskError):
"""Transient errors that may succeed on retry."""
def __init__(self, message: str, context: dict[str, Any], cause: BaseException, **kwargs):
super().__init__(message, context, cause, non_retryable=False, **kwargs)
class TaskNotFoundError(ProcessTaskFatalError):
pass
class TaskRunNotReadyError(ProcessTaskTransientError):
"""The TaskRun row is not yet visible to this activity.
Typically the creating transaction has not committed by the time the activity's
first read runs (the run was created inside an enclosing transaction.atomic block).
Retryable so the activity's existing retry policy recovers once the row is visible.
Intentionally does not capture to error tracking — this is an expected transient
window the retry absorbs, not a fault worth an issue.
"""
def __init__(self, message: str, context: dict[str, Any]):
# Bypass ProcessTaskTransientError.__init__ to pass cause=None, which skips the
# capture_exception() call in ProcessTaskError — avoiding error-tracking noise.
ProcessTaskError.__init__(self, message, context, None, non_retryable=False)
class GitHubRateLimitedError(ProcessTaskTransientError):
"""GitHub rate-limited a call (or our egress budget shed it before it was sent).
An expected, recoverable condition rather than a fault. Kept retryable so the
activity's retry policy recovers once the limit window passes, but intentionally
not captured to error tracking — there is nothing to investigate, and capturing
it mints a noisy issue for something we expect to happen. ``retry_after`` (seconds)
drives ``next_retry_delay`` so the retry lands after the window instead of burning
every attempt inside it, and is folded into the message so the surfaced error names
a real wait instead of an empty reset time.
"""
def __init__(self, message: str, context: dict[str, Any], retry_after: int):
# Bypass ProcessTaskTransientError.__init__ to pass cause=None with capture=False,
# skipping the capture_exception() call in ProcessTaskError (mirrors TaskRunNotReadyError).
ProcessTaskError.__init__(
self,
message,
context,
None,
capture=False,
non_retryable=False,
next_retry_delay=timedelta(seconds=retry_after),
)
class TaskInvalidStateError(ProcessTaskFatalError):
pass
class SandboxProvisionError(ProcessTaskTransientError):
"""Failed to provision sandbox environment."""
pass
class SandboxNotFoundError(ProcessTaskFatalError):
"""Sandbox does not exist."""
pass
class SandboxExecutionError(ProcessTaskTransientError):
"""Error during sandbox command execution."""
pass
class SandboxMissingRepositoryError(ProcessTaskFatalError):
"""The repository directory the agent-server needs as its cwd is absent from the sandbox.
Happens when a run reaches agent-server start without a clone — no snapshot restored and no
usable GitHub credentials. Retrying cannot make the directory appear, so fail immediately
with the real reason instead of burning health-check timeouts on a server that can never
open a session.
"""
pass
class SandboxNotRunningError(SandboxExecutionError):
"""Sandbox is not in a running state."""
pass
class SandboxTimeoutError(ProcessTaskTransientError):
"""Sandbox operation timed out."""
pass
class SandboxCleanupError(ProcessTaskTransientError):
"""Error during sandbox cleanup/destruction."""
pass
class SnapshotNotFoundError(ProcessTaskTransientError):
"""Snapshot does not exist."""
pass
class SnapshotNotReadyError(ProcessTaskTransientError):
"""Snapshot exists but is not ready for use."""
pass
class SnapshotCreationError(ProcessTaskTransientError):
"""Failed to create snapshot."""
pass
class SnapshotTimeoutError(ProcessTaskTransientError):
"""Transient timeout/connection error while creating a snapshot; safe to retry."""
pass
class RepositoryCloneError(ProcessTaskTransientError):
"""Failed to clone repository."""
pass
class RetryableRepositorySetupError(ProcessTaskTransientError):
"""Failed to setup repository (install dependencies, etc)."""
pass
class GitHubIntegrationError(ProcessTaskFatalError):
"""GitHub integration not found or invalid."""
pass
class GitHubAuthenticationError(ProcessTaskFatalError):
"""Failed to authenticate with GitHub."""
pass
class CredentialUnavailableError(ProcessTaskFatalError):
"""A sandbox credential can never be resolved again for this run — the backing
integration row was deleted mid-run or the user must re-authorize.
Not retriable, and an expected customer-initiated state rather than a systemic
failure, so it is not captured to error tracking.
"""
def __init__(self, message: str, context: dict[str, Any], cause: Exception | None = None):
ProcessTaskError.__init__(self, message, context, cause, capture=False, non_retryable=True)
class PersonalAPIKeyError(ProcessTaskTransientError):
"""Failed to create or inject personal API key."""
pass
class OAuthTokenError(ProcessTaskTransientError):
"""Failed to create OAuth access token."""
pass
class TaskExecutionFailedError(ProcessTaskError):
"""Task execution completed but with non-zero exit code."""
def __init__(
self,
message: str,
exit_code: int,
stdout: str = "",
stderr: str = "",
context: Optional[dict[str, Any]] = None,
cause: Optional[Exception] = None,
non_retryable: bool = False,
):
self.exit_code = exit_code
self.stdout = stdout
self.stderr = stderr
if cause is None:
cause = RuntimeError(f"Task failed with exit code {exit_code}")
super().__init__(message, context or {}, cause, non_retryable=non_retryable)