← All code samples

SRE → investigate alert

Start an SRE investigation from an alert that already exists in the inbox, then wait until the investigation has RCA fields. Requires SRE on the workspace and an API token.

Step What the SDK does
1. List or get alert list_alerts / get_alert
2. Investigate and wait investigate_alert_and_wait (starts, then polls)
3. Optional Use guild_session_id with Aiden export APIs for the session file

Prefer this path when the alert is already in SRE and you want the investigation record plus structured RCA. Use Ask → artifact when you only have a free-text prompt and no alert id. To refresh the inbox first, see Pull alerts.

Investigation status

Status Meaning
queued, running Still working. Keep waiting.
completed RCA is ready. Read structured_rca and result_summary.
draft Triage saved with gaps. Read triage_metadata (more_info_requested, more_info_notes).
resolved Operator closed the thread.
failed, cancelled Stop. The wait helper raises.

Minimal — list, investigate, wait

from stackgen import StackgenClient, StackgenConfig

client = StackgenClient(
    StackgenConfig(
        base_url="https://app.stackgen.com",
        api_token="stackgen_…",
        workspace_id="<workspace-uuid>",
    )
)

alerts = client.sre.list_alerts(status="active", q="checkout CPU")
rows = alerts.get("alerts") or alerts.get("items") or []
alert_id = rows[0]["id"]

inv = client.sre.investigate_alert_and_wait(
    alert_id,
    operator_message="Investigate checkout CPU in prod",
)
print(inv.get("result_summary"))
rca = inv.get("structured_rca") or {}
print((rca.get("plain_summary") or {}).get("verdict"))
print(inv.get("guild_session_id"))

How to run

export STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>

python examples/sre/investigate-alert.py --q 'checkout CPU' --poll

Add --list-only to check access without starting an investigation.

Complete script

#!/usr/bin/env python3
"""SRE: list alerts → investigate → wait for the RCA.

Proves the alert-already-in-inbox path. Use --list-only when the tenant
has no alerts yet (HTTP 200 + empty list is success).

Documented at:
  https://appcd-dev.github.io/stackgen-sdk/code-samples/sre-investigate-alert/

Credentials (flags override env):
  STACKGEN_URL, STACKGEN_TOKEN, STACKGEN_WORKSPACE_ID (or STACKGEN_ORG_ID / STACKGEN_PROJECT)

Usage:

  pip install -e ./clients/python

  python examples/sre/investigate-alert.py --list-only

  python examples/sre/investigate-alert.py --q 'checkout' --poll

  python examples/sre/investigate-alert.py --alert-id <uuid> --poll
"""

from __future__ import annotations

import argparse
import json
import os
import sys

from stackgen import StackgenClient, StackgenConfig
from stackgen.errors import StackgenError


def _org_id(cli_value: str | None) -> str:
    return (
        (cli_value or "").strip()
        or os.environ.get("STACKGEN_WORKSPACE_ID", "").strip()
        or os.environ.get("STACKGEN_ORG_ID", "").strip()
        or os.environ.get("STACKGEN_PROJECT", "").strip()
        or os.environ.get("STACKGEN_PROJECT_ID", "").strip()
    )


def _config(args: argparse.Namespace) -> StackgenConfig:
    base = (args.base_url or os.environ.get("STACKGEN_URL", "")).strip()
    token = (args.api_token or os.environ.get("STACKGEN_TOKEN", "")).strip()
    org = _org_id(args.workspace_id)
    if not base or not token:
        raise SystemExit(
            "set --base-url/--api-token or STACKGEN_URL / STACKGEN_TOKEN"
        )
    return StackgenConfig(
        base_url=base,
        api_token=token,
        workspace_id=org,
        timeout_seconds=args.timeout_seconds,
        poll_interval_seconds=args.poll_interval_seconds,
    )


def _alert_rows(payload: object) -> list[dict]:
    if not isinstance(payload, dict):
        return []
    rows = payload.get("alerts") or payload.get("items") or []
    return [r for r in rows if isinstance(r, dict)]


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--base-url", default="")
    parser.add_argument("--api-token", default="")
    parser.add_argument("--workspace-id",
        "--org-id", default="")
    parser.add_argument("--status", default="active", help="Alert status filter")
    parser.add_argument("--q", default="checkout", help="Alert search query")
    parser.add_argument("--alert-id", default="", help="Skip list; investigate this id")
    parser.add_argument(
        "--list-only",
        action="store_true",
        help="List matching alerts and exit 0 (empty list is OK)",
    )
    parser.add_argument(
        "--poll",
        action="store_true",
        help="After investigate, wait until the RCA is ready",
    )
    parser.add_argument(
        "--export-session",
        action="store_true",
        help="If guild_session_id is set, call aiden.export_session(parts=report)",
    )
    parser.add_argument("--timeout-seconds", type=int, default=1800)
    parser.add_argument("--poll-interval-seconds", type=int, default=5)
    parser.add_argument(
        "--operator-message",
        default="Investigate checkout CPU in prod",
    )
    args = parser.parse_args()

    client = StackgenClient(_config(args))

    alert_id = args.alert_id.strip()
    if not alert_id:
        alerts = client.sre.list_alerts(status=args.status, q=args.q)
        if not isinstance(alerts, dict):
            print(f"error: unexpected list_alerts shape: {alerts!r}", file=sys.stderr)
            return 1
        items = _alert_rows(alerts)
        print("list_alerts:", json.dumps(alerts, indent=2, default=str))
        print(f"count={len(items)}", file=sys.stderr)
        if args.list_only:
            return 0
        if not items:
            print(
                "error: no matching alert (pass --alert-id or use --list-only)",
                file=sys.stderr,
            )
            return 1
        alert_id = str(items[0].get("id") or "")
        if not alert_id:
            print("error: alert item missing id", file=sys.stderr)
            return 1

    if not args.poll:
        started = client.sre.investigate_alert(
            alert_id,
            operator_message=args.operator_message,
        )
        print("investigate:", json.dumps(started, indent=2, default=str))
        return 0

    investigation = client.sre.investigate_alert_and_wait(
        alert_id,
        operator_message=args.operator_message,
    )
    print("investigation:", json.dumps(investigation, indent=2, default=str))

    plain = investigation.get("structured_rca") if isinstance(investigation, dict) else None
    if isinstance(plain, dict):
        summary = plain.get("plain_summary")
        if isinstance(summary, dict):
            print(
                "plain_summary:",
                json.dumps(summary, indent=2, default=str),
            )
        root = plain.get("root_cause")
        if root:
            print(f"root_cause={root}", file=sys.stderr)

    session_id = investigation.get("guild_session_id") if isinstance(investigation, dict) else None
    if args.export_session and session_id:
        export = client.aiden.export_session(str(session_id), parts="report")
        print("export:", json.dumps(export, indent=2, default=str))
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except StackgenError as exc:
        print(f"error: {exc}", file=sys.stderr)
        if "401" in str(exc) or "missing identity" in str(exc).lower():
            print(
                "hint: SRE /app/sre routes need the edge to inject workspace "
                "identity (X-Appcd-Org). If Aiden works but SRE returns 401, "
                "the mothership app proxy is not forwarding PAT identity.",
                file=sys.stderr,
            )
        raise SystemExit(1)

Notes

  • investigate_alert_and_wait starts the run and polls until completed, draft, or resolved. It raises on failed or cancelled.
  • If the alert id is unknown, the API returns 404. Pull alerts first with Pull alerts.
  • Optional fields: force_new, operator_message, remediation_requested, mode.
  • When guild_session_id is set, download session-report.md or call client.aiden.export_session(session_id, parts="report").

Related: Pull alerts · Triage alerts · Ask → artifact