AWS DevOps Agent に障害の調査結果から GitHub Issue を起票させてみた

AWS DevOps Agent に障害の調査結果から GitHub Issue を起票させてみた

チャットとインシデントレスポンスの調査、どちらからも起票できました。ランブックで手順も指定可能。
2026.08.24

こんにちは、製造ビジネステクノロジー部の若槻です。

以前、AWS DevOps Agent に Lambda のタイムアウトを調査させて、修正プルリクエストまで作らせる記事を書きました。

ただ実際の運用では、コードの修正までは踏み込まずチケットの起票だけしたい場面もあると思います。

そこで今回は「障害の調査結果を GitHub Issue に起票する」を組んでみました。ツールの選び方から権限まで、つまずいた点を含めて残しておきます。

結論

  • 起票できました。ネイティブの GitHub 連携は読み取り専用なので、GitHub 公式のリモート MCP サーバーを接続して issue_write を許可します
  • create_issue というツールは存在しません。現行の GitHub MCP サーバーは読み書きが issue_read / issue_write に統合されています。推測でツール名を書くと動きません
  • ツールを許可しただけでは足りません。GitHub App の権限に Issues の書き込みがないと 403 Resource not accessible by integration で失敗します。DevOps Agent 側と GitHub 側の両方を通す必要があります
  • 起票された Issue には、根拠ログと根拠コードの両方が引用され、修正方針まで具体的に書かれていました。依頼していない改善提案まで付いてきました
  • インシデントレスポンスの調査(investigation)でも起票できます。手順をランブックとしてスキルに書いておくとそのとおりに動き、同じ事象の Issue が既にあれば起票を見送り、なければ新規に起票しました

前提:ネイティブ連携では書き込めない

前回のプルリクエスト作成で分かったことですが、DevOps Agent のネイティブな GitHub 連携がエージェントに渡すツールは、アクセスレベルを「読み取りと書き込み」で登録していても読み取り専用の 14 個だけです。

つまり Issue の起票も、GitHub 公式のリモート MCP サーバーを別途接続しないと不可能です。今回もこの MCP サーバーを使います。MCP サーバー自体の登録手順は前回の記事にあるので、本記事では起票までの道筋に集中します。

起票が通るまでの 2 つの関門

Issue が実際に作られるまでには、通らなければならない関門が 2 つあります。

①はエージェントに渡すツールを決める関門です。許可しなかったツールはエージェントから見えません。

②はそのツールが GitHub 側で通るかを決める関門です。ツールを渡しても、App に権限がなければ API が弾きます。

後述しますが、②を忘れて①だけ設定したせいで、最初の実行は起票に失敗しました。

検証用のリソースを作る

調査対象として、意図的に壊した Lambda 関数を AWS CDK で作ります。SNS トピックへ通知を発行するのに、実行ロールへ sns:Publish を付与していない関数です。topic.grantPublish() の呼び忘れという、実際にありがちな IaC の欠陥です。

原因がコードの欠陥である点がポイントです。ログだけを見ても分からず、リポジトリのコードまで読まないと結論が出ません。起票された Issue が実用に足るかを見るのにちょうどよい題材です。

ハンドラーは通知を発行するだけです。

lib/constructs/devops-agent-issue-verification/alert-notifier-function/function.ts
import { PublishCommand, SNSClient } from "@aws-sdk/client-sns";

const client = new SNSClient({});

/**
 * SNS トピックへ通知を発行する検証用ハンドラー
 *
 * 実行ロールに sns:Publish を付与していないため、意図的に
 * AuthorizationErrorException を発生させる。
 */
export const handler = async (): Promise<void> => {
  const topicArn = process.env.TOPIC_ARN;

  console.log(`Publishing an alert notification to ${topicArn}.`);

  await client.send(
    new PublishCommand({
      TopicArn: topicArn,
      Subject: "Alert notification",
      Message: "An alert was detected.",
    }),
  );

  console.log("Published an alert notification.");
};

コンストラクトでは topic.grantPublish() を意図的に呼びません。

lib/constructs/devops-agent-issue-verification/alert-notifier-function/index.ts
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import * as logs from "aws-cdk-lib/aws-logs";
import * as sns from "aws-cdk-lib/aws-sns";
import { Construct } from "constructs";

/**
 * 検証用 Lambda 関数の実装
 *
 * SNS トピックへの通知発行を行うが、実行ロールに sns:Publish を付与していないため、
 * 意図的に AuthorizationErrorException を発生させる。DevOps Agent にこの障害を調査させ、
 * GitHub Issue を起票させるための検証用リソース。検証専用。
 */
export class AlertNotifierFunctionConstruct extends Construct {
  public readonly function: nodejs.NodejsFunction;

  constructor(scope: Construct, id: string) {
    super(scope, id);

    const topic = new sns.Topic(this, "Topic"); // 通知先のトピック。発行権限は意図的に付与しない

    const functionName = "devops-agent-issue-verification-alert-notifier";

    /**
     * 関数のロググループ
     *
     * ロググループ名を明示せず CDK の自動命名に任せると `/aws/lambda/` 配下に
     * 作られないため、エージェントがロググループを探すのに時間を使う。
     * 規約どおりの名前を明示して発見しやすくする。
     */
    const logGroup = new logs.LogGroup(this, "LogGroup", {
      logGroupName: `/aws/lambda/${functionName}`,
      retention: logs.RetentionDays.ONE_WEEK,
      removalPolicy: cdk.RemovalPolicy.DESTROY, // 検証用のため、スタック削除時に削除する
    });

    this.function = new nodejs.NodejsFunction(this, "Default", {
      functionName,
      entry:
        "lib/constructs/devops-agent-issue-verification/alert-notifier-function/function.ts",
      handler: "handler",
      runtime: lambda.Runtime.NODEJS_24_X,
      timeout: cdk.Duration.seconds(10),
      logGroup,
      environment: {
        TOPIC_ARN: topic.topicArn,
      },
      // topic.grantPublish() を意図的に呼んでいない(障害の原因)
    });
  }
}

ロググループ名を明示している理由は後述します。ここを省略したせいで一度検証に失敗しました。

許可するツールを決める

GitHub MCP サーバーが公開しているツールは 47 個あります。マネジメントコンソールの MCP サーバーツールを選択 画面で全量を確認できます。

このうち Issue 関連は 9 個でした。

ツール 用途
issue_write Issue の作成・更新
issue_read Issue の参照
list_issues Issue の一覧
search_issues Issue の検索
add_issue_comment コメントの追加
list_issue_fields フィールドの一覧
list_issue_types タイプの一覧
sub_issue_write サブ Issue の作成・更新
assign_copilot_to_issue Issue を Copilot に割り当て

まず注意したいのが、create_issue というツールは存在しないことです。現行の GitHub MCP サーバーは読み書きが issue_read / issue_write に統合されています。推測でツール名を書くと動きません。

そしてもう一つ、assign_copilot_to_issue は外しました。名前は Issue 系ですが、これは Issue を Copilot に割り当ててコードを書かせるツールです。起票までを任せたいだけなら不要なので、create_pull_request_with_copilotrequest_copilot_review とあわせて対象外にしています。

今回許可したのは 3 つだけです。

tools: ["issue_write", "list_issues", "search_issues"]

list_issuessearch_issues を入れているのは、既存 Issue を確認させて重複起票を避けられるようにするためです。読み取りだけなので害はありません。

ツール許可リストを AWS CDK で設定する

MCP サーバーの登録自体はコンソール限定ですが、Association(ツール許可リスト)は AWS::DevOpsAgent::Association で作成できます。

lib/constructs/devops-agent-issue-verification/github-mcp-association/index.ts
import * as devopsagent from "aws-cdk-lib/aws-devopsagent";
import { Construct } from "constructs";

export interface GitHubMcpServerProps {
  serviceId: string; // コンソールで OAuth 3LO 登録した GitHub MCP サーバーのサービス ID
  tools: string[]; // Agent Space に許可する MCP ツール
}

interface GitHubMcpAssociationConstructProps {
  agentSpaceId: string;
  gitHubMcpServer: GitHubMcpServerProps;
}

/**
 * GitHub MCP サーバーと Agent Space の関連付けの実装
 *
 * ネイティブの GitHub 連携が提供するツールは読み取り専用のため、Issue を
 * 起票させるには GitHub MCP サーバーを別途接続する必要がある。
 * tools には Issue の起票に必要なツールだけを指定し、コードを書き換えられる
 * ツール(push_files や create_pull_request など)は渡さない。
 * @see https://docs.aws.amazon.com/devopsagent/latest/userguide/configuring-integrations-and-knowledge-connecting-mcp-servers.html
 */
export class GitHubMcpAssociationConstruct extends Construct {
  public readonly association: devopsagent.CfnAssociation;

  constructor(
    scope: Construct,
    id: string,
    props: GitHubMcpAssociationConstructProps,
  ) {
    super(scope, id);

    const { agentSpaceId, gitHubMcpServer } = props;
    const { serviceId, tools } = gitHubMcpServer;

    this.association = new devopsagent.CfnAssociation(this, "Default", {
      agentSpaceId,
      serviceId,
      configuration: {
        mcpServer: {
          tools,
        },
      },
    });
  }
}

スタックはこれだけです。

lib/devops-agent-issue-verification-stack.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

import { AppParameter } from "../bin/parameter";
import { AlertNotifierFunctionConstruct } from "./constructs/devops-agent-issue-verification/alert-notifier-function";
import { GitHubMcpAssociationConstruct } from "./constructs/devops-agent-issue-verification/github-mcp-association";
import { SlowTaskFunctionConstruct } from "./constructs/devops-agent-issue-verification/slow-task-function";

/**
 * DevOps Agent の GitHub Issue 起票検証用スタック
 *
 * 意図的に AuthorizationErrorException を発生させる Lambda 関数を作成し、DevOps Agent に
 * 調査と GitHub Issue の起票を行わせる。コードを書き換えるツールは許可しない。
 * 検証が終わったら cdk destroy で削除する。
 */
export class DevOpsAgentIssueVerificationStack extends cdk.Stack {
  constructor(
    scope: Construct,
    id: string,
    props: AppParameter & cdk.StackProps,
  ) {
    super(scope, id, props);

    const { devOpsAgentIssueVerification } = props;
    const { agentSpaceId, gitHubMcpServer } = devOpsAgentIssueVerification;

    // 意図的に AuthorizationErrorException を発生させる Lambda 関数を作成
    new AlertNotifierFunctionConstruct(this, "AlertNotifierFunction");

    // 意図的に Task timed out を発生させる Lambda 関数を作成
    new SlowTaskFunctionConstruct(this, "SlowTaskFunction");

    // GitHub MCP サーバーを Agent Space に関連付け(ツール許可リスト)
    new GitHubMcpAssociationConstruct(this, "GitHubMcpAssociation", {
      agentSpaceId,
      gitHubMcpServer,
    });
  }
}

デプロイ後、許可リストを CLI で確認します。

$ aws devops-agent list-associations \
    --agent-space-id <agent-space-id> \
    --query 'associations[?configuration.mcpserver].configuration.mcpserver'
[
    {
        "tools": [
            "issue_write",
            "list_issues",
            "search_issues"
        ]
    }
]

障害を起こす

Lambda 関数を 3 回呼び出します。

$ for i in 1 2 3; do
    aws lambda invoke \
      --function-name devops-agent-issue-verification-alert-notifier \
      --payload '{}' /tmp/out-$i.json --query 'FunctionError' --output text
  done
Unhandled
Unhandled
Unhandled

想定どおり失敗しました。エラー型は AccessDeniedException ではなく AuthorizationErrorException(SNS 固有)です。

$ aws logs filter-log-events \
    --log-group-name /aws/lambda/devops-agent-issue-verification-alert-notifier \
    --filter-pattern "AuthorizationErrorException" \
    --query 'length(events)'
3

起票を依頼する

Operator Web App のチャットで依頼します。

Lambda 関数 devops-agent-issue-verification-alert-notifier がエラーになっています。ログは /aws/lambda/devops-agent-issue-verification-alert-notifier にあります。ログと <owner>/security-infra リポジトリのコードを調べて原因を特定し、そのリポジトリに GitHub Issue を起票してください。Issue には原因、根拠にしたログとコードの箇所、修正方針を含めてください。コードの修正やプルリクエストの作成は行わないでください。

約 3 分で起票されました。


Issue 番号と URL を返し、調査内容の要約も添えている

エージェントは 2 つのサブエージェントを並行で走らせ、ログ調査とコード調査を同時に進めていました。使用したのはツール 19 個、エージェント 2 個、スキル 2 個です。

起票された Issue の中身

依頼では、原因・根拠にしたログとコードの箇所・修正方針の 3 点を含めるよう指定しました。その 3 点がすべて揃っていました。

根拠ログはロググループ名・時刻・エラーメッセージ全文に加え、発生パターンまで書かれています。

発生パターン:

  • 3回の呼び出しが約2秒間に集中し、すべて同一エラーで失敗(失敗率 100%)
  • 初回呼び出しはコールドスタート(Init: 283ms)、2回目以降はウォーム実行

根拠コードは 2 ファイルを引用し、失敗する行まで指しています。

修正方針は追加する 1 行と、それによって生成される IAM ポリシーまで示しています。

topic.grantPublish(fn);  // ← この行を追加

これにより CDK が Lambda 実行ロールに対して以下の IAM ポリシーを自動生成・アタッチする。

そして依頼していない指摘まで付いてきました。末尾の「補足」で、handler に try-catch がないため権限以外のエラーも無通知で失敗すると書き添えています。

起票された Issue の全体がこちらです。


概要・エラーログ(根拠)・原因となるコード箇所・影響リソース・修正方針・補足という構成。ARN とアカウント ID は黒塗りにしている

このまま人間が読んで着手できる水準だと思います。調査した本人が書いているので、転記のロスがありません。

起票者はユーザーとして記録される

GitHub 側の記録も見ておきます。起票者は認可したユーザー本人で、そこに「with (GitHub App 名)」が併記されます。


アイコンと名前はユーザーのもので、「with devops-agent-github-mcp-…」が続く

MCP サーバーを OAuth 3LO で登録しているためです。発行される user access token は「このユーザーとして、この GitHub App 経由で」という扱いになるので、誰の代理でどのアプリが操作したかの両方が残ります。

API でも同じで、user.typeBot ではなく User です。App の情報は performed_via_github_app に入ります。

$ gh api repos/<owner>/security-infra/issues/110 \
    --jq '{user: .user.login, type: .user.type, app: .performed_via_github_app.slug}'
{
  "user": "<owner>",
  "type": "User",
  "app": "devops-agent-github-mcp-..."
}

エージェントが起票した Issue を Bot として機械的に振り分けたい場合は、この点に注意が必要です。起票者はあくまで人間として記録されるので、判別するなら performed_via_github_app を見ることになります。

許可していないツールはエージェントから見えない

今回は Issue 系の 3 つしか許可していないので、コードを書き換えるツールは渡っていません。念のため、同じチャットで修正プルリクエストにまとめられるかを尋ねてみました。


使えるツールの一覧を挙げ、許可設定との対応まで説明している

結論:プルリクエストの作成はできません。

ツール名 操作できること
GitHubMcpServer_issue_write Issue の作成・更新のみ

以上が GitHub MCP サーバー経由で利用できるツールの全てです。

なぜプルリクエストを作れないか
ブランチの作成、ファイルの書き込み・コミット、プルリクエストの作成に相当するツールは、いずれも許可リストに含まれていないため呼び出せません。

ジャーナルでも裏を取りました。実際に呼ばれた MCP ツールは GitHubMcpServer_issue_write だけで、create_branchpush_files の呼び出しはゼロでした。プロンプトで頼む運用と違い、渡していないツールはそもそも選択肢に出てきません。

インベスティゲーションでも同じことができる

ここまではチャットでの依頼でした。DevOps Agent にはインシデントレスポンスの調査(investigation)という別の経路もあります。こちらからも起票できるのか、そして運用手順を書いたランブックに従わせられるのかを確認しました。

ランブックを用意する

調査結果の扱い方をスキルとして与えます。重複確認から起票、報告までを手順に書きました。

skills/incident-issue-runbook/SKILL.md
---
name: incident-issue-runbook
description: 障害調査の結果を GitHub Issue に起票するための手順。調査で根本原因を特定したあと、その内容をチケットとして残す必要があるときに使用する。コードの修正やプルリクエストの作成は行わない。
---

# 調査結果の Issue 起票ランブック

障害の調査が完了したら、この手順に従って結果を GitHub Issue として残すこと。

## 前提

- 起票先のリポジトリは `<owner>/security-infra`
- **コードの修正やプルリクエストの作成は行わない**。修正は人間が判断して実施する
- 起票は調査の結論が出たあとに 1 回だけ行う

## ステップ 1: 重複を確認する

`search_issues` で同じ事象の Issue が既に存在しないかを確認する。存在する場合は新規に起票せず、既存 Issue の番号を報告する。

## ステップ 2: Issue を起票する

`issue_write` で Issue を作成する。タイトルは「どのリソースで何が起きているか」が一目で分かる形にする。

本文には以下をすべて含めること。

1. **概要** — 何が起きているかを 2〜3 文で
2. **根拠ログ** — ロググループ名、確認した時刻、エラーメッセージの引用
3. **根拠コード** — リポジトリ内のファイルパスと該当箇所の引用
4. **修正方針** — 具体的に何を変更すればよいか
5. **調査で確認した範囲** — 調べたリソースと、調べていない範囲

## ステップ 3: 報告する

起票した Issue の番号と URL を報告する。あわせて、修正は人間が実施する必要がある旨を明記する。

これを Agent Space に取り込みます。エージェントタイプは GENERIC にしました。

aws devops-agent create-asset \
  --agent-space-id <agent-space-id> \
  --asset-type skill \
  --metadata '{"agent_types":["GENERIC"],"status":"ACTIVE"}' \
  --content '{"sourceUrl":{"url":"https://github.com/<owner>/security-infra/tree/main/skills/incident-issue-runbook"}}'

調査を手動で開始する

調査は Operator Web App のダッシュボードから手動で開始できます。自由記述の入力欄と、最新のアラーム / CPU 使用率が高い / エラー率の急上昇 のプリセットが並んでいます。

調査を開始を押すと確認ダイアログが出て、調査の詳細・出発点・発生時刻を詰められます。


出発点にログの手がかりを書いておくと、エージェントが探索に使う時間を減らせる

チャットより深く掘る

同じ障害を調査させたところ、動きがだいぶ違いました。

観点 チャット インベスティゲーション
サブエージェント数 2 6(並列)
所要時間 約 3 分 約 9 分
主なデータソース CloudWatch Logs Metrics・CloudTrail・IAM・SNS
証拠の扱い 文章 root cause / symptom / gap / observation に分類
成果物 Issue 起票 即時対応と恒久対策を分けた緩和策


6 つのサブエージェントの結果と、証拠に付いた分類バッジ

とくに感心したのが、手元の情報が欠けても他の材料で埋めにいくことです。この調査ではいくつかの取得が失敗しましたが、CloudWatch Metrics(エラー率 100%、過去 7 日間で成功 0 件)、CloudTrail(CreateFunction イベント)、IAM(実行ロールに AWSLambdaBasicExecutionRole のみ)を突き合わせて原因を特定しました。取得できなかった事実は gap として明示していました。

なお、この「取得できなかったもの」についてエージェントは Service Control Policy による拒否だと説明していましたが、これは誤りでした。詳しくは後述します。


根本原因と緩和策。既存 Issue への参照も入っている

ランブックの重複確認が効いた

そして肝心の Issue 起票です。タイムラインを見ると、ランブックを読み込んで手順どおりに動いていました。


ランブックを読み込み、ステップ 1 の重複確認で既存 Issue を発見している

ステップ1: 重複確認
既存Issueの確認を実行します。
1 件の GitHubMcpServer_search_issues 呼び出し

既存Issueを発見
同じ事象のIssueが既に存在しました
(中略)
私の調査結果と完全に一致しています。新規Issueの起票は不要です。

チャット経路で起票した Issue を見つけて、重複を避けました。ランブックのステップ 1 が設計どおり働いた形です。

さらに面白いのが、その Issue が調査の証拠として使われたことです。

既存Issue #110: CloudWatch Logsから実際の AuthorizationErrorException エラーメッセージを確認

自分のログ検索が一部失敗していたぶんを、先に残されたチケットから補っています。

重複しない障害なら新規に起票する

重複確認で止まったので、別の症状の障害も用意しました。処理時間 10 秒に対してタイムアウトを 3 秒にした関数です。権限不足とは症状が違うので、既存 Issue には該当しません。

lib/constructs/devops-agent-issue-verification/slow-task-function/index.ts
    this.function = new nodejs.NodejsFunction(this, "Default", {
      functionName,
      entry:
        "lib/constructs/devops-agent-issue-verification/slow-task-function/function.ts",
      handler: "handler",
      runtime: lambda.Runtime.NODEJS_24_X,
      timeout: cdk.Duration.seconds(3), // 処理時間より短く、意図的にタイムアウトさせる
      logGroup,
    });

これを調査させたところ、約 5 分半で新規に起票されました


タイムアウトの Issue が新規に起票された。Issues のカウントが 2 になっている

Issue の中身も同水準でした。3 回分の RequestId と Duration: 3000.00 ms / Status: timeout を個別に引用し、ログから読み取れることまで書いています。

  • 処理開始のログ("Start processing. This task takes 10 seconds.")は記録されている
  • 処理完了のログ("Finished processing.")は一切記録されていない
  • すべての実行が正確に 3000ms でタイムアウトしている

これで両方の分岐が確認できました。

条件 ランブックの挙動 結果
同じ事象の Issue がある ステップ 1 でヒット 起票せず既存 Issue を報告
同じ事象の Issue がない ステップ 1 を通過 新規に起票

「同じ事象が既にあれば起票しない」という運用ルールが、そのままエージェントの挙動になりました。ランブックを Git で管理しておけば、起票の粒度や本文の書式をチームの流儀に寄せられます。

ツールの許可リストは Agent Space 単位なので、調査経路でも渡っているのは Issue 系の 3 つだけです。緩和策として topic.grantPublish(fn) の追加を提案してきますが、実装は人間の側に残ります。

つまずいた点

ここからは、うまくいかなかった過程です。同じ構成を組む方には、こちらのほうが役に立つかもしれません。

許可リストだけでは起票できなかった

最初の実行では、調査は完了したのに起票が失敗しました。エージェントの説明はこうでした。

GitHub MCP サーバーの issue_write ツールが READ_ONLY として分類されており、書き込み操作が制限されています

しかしジャーナルに残っていた実際のツールエラーは別物でした。

Tool 'issue_write' from custom server 'GitHubMcpServer' failed:
failed to create issue: POST <redacted> 403 Resource not accessible by integration []

Resource not accessible by integrationGitHub App の権限不足を示す GitHub 側のメッセージです。DevOps Agent の設定とは無関係でした。

原因は単純で、GitHub App の Repository permissions が Contents と Pull requests だけで、Issues が No access だったことです。以前この App を修正プルリクエスト用に作ったので、Issues を付けていませんでした。

App の権限に Issues: Read and write を追加し、インストール側で新しい権限を承認したところ、同じ依頼で起票できるようになりました。

これが冒頭に書いた 2 つの関門です。①のツール許可リストを通っても、②の GitHub App 権限で止まります。エラーメッセージがどちら側のものかを見分けられると切り分けが早いので、Resource not accessible by integration は GitHub 側、と覚えておくとよさそうです。

エージェントの自己診断を信じてはいけない

上記のとおり、エージェントは失敗原因を自分の設定のせいだと説明しました。しかも toolDetailstoolClassification が並んだ JSON まで提示してきました。

そこで list-associations の実際の出力を確認したところ、toolDetails というフィールドは存在しませんでした

{
  "tools": ["issue_write", "list_issues", "search_issues"]
}

toolClassification 自体は実在する概念で、以前の検証では確認しています。

https://dev.classmethod.jp/articles/aws-devops-agent-multi-layer-security-verification/

ただ今回のケースでは、エージェントが提示した JSON を裏付けられませんでした。ジャーナルでツールの生の戻り値を見れば、403 Resource not accessible by integration という答えがそのまま書いてあります。

同じことが調査経路でも起きました。調査エージェントは、情報が取得できなかった理由を Service Control Policy による拒否だと説明し、ポリシー ID まで挙げていました。しかし CloudTrail を見ると話が違いました。

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=Username,AttributeValue=monitorAssociationRoleSession \
  --start-time <> --end-time <>

実際に記録されていた拒否メッセージはこれです。

User: arn:aws:sts::<account-id>:assumed-role/<agent-space-role>/monitorAssociationRoleSession
is not authorized to perform: lambda:GetFunction on resource: <function-arn>
because no identity-based policy allows the lambda:GetFunction action

because no identity-based policy allows です。SCP による明示的な拒否(with an explicit deny in a service control policy)ではなく、Agent Space の監視ロールにその権限が付与されていないだけの暗黙的な拒否でした。

しかも、拒否されていたのは Lambda の関数情報の取得だけです。CloudWatch Logs は読めていました。

操作 結果
DescribeLogGroups 8 件すべて成功
StartQuery 5 件成功 / 3 件 MalformedQueryException
GetQueryResults 10 件成功
GetFunction 3 件 AccessDenied
GetFunctionConcurrency 1 件 AccessDenied

ログ検索が一部失敗していたのも権限ではなくクエリの不備でした。ロググループを作った直後で、保持期間の範囲外を指定していたためです。

Query's end date and time is either before the log groups creation time
or exceeds the log groups log retention settings

つまり「SCP でログが読めなかった」という説明は、原因の種類も対象も外していたわけです。根本原因の特定自体は正しかったので、報告の説得力に引っぱられて信じてしまいそうになります。エージェントの原因説明は、それらしく読めても一次情報で確認したほうがよいという教訓が、2 回続きました。

ロググループ名を規約から外すと調査が終わらない

もう一つのつまずきです。最初はロググループ名を CDK の自動命名に任せていました。すると /aws/lambda/ 配下ではない名前になります。

この状態で調査を依頼したところ、エージェントがロググループを探し続けて 28 分経っても調査が完了しませんでした。トレースにはこう残っていました。

Log group not found. Let me check if there's a different log group name or account

The log group doesn't exist in that account. Let me check all accounts and regions to find the right location

The DevOpsAgentIssueVerificat log group wasn't in the first page. Let me search more specifically

Found it. The log group is DevOpsAgentIssueVerification-AlertNotifierFunctionLogGroup... (no leading /aws/lambda/, it's a custom log group)

describe_log_groups を何度も叩き、アカウントとリージョンを横断して探していました。最終的には見つけたものの、その後サブエージェントのレコードが失われて止まりました。

関数名を明示してロググループ名を /aws/lambda/<関数名> に揃えたところ、探索を飛ばして一発でログにたどり着き、3 分で完了しました。

const functionName = "devops-agent-issue-verification-alert-notifier";

const logGroup = new logs.LogGroup(this, "LogGroup", {
  logGroupName: `/aws/lambda/${functionName}`,
  // ...
});

人間なら「たぶんこれだろう」で済むところを、エージェントは総当たりで探しに行きます。回避策は 2 つあると思います。

一つは、リソースの命名を規約どおりに揃えておくことです。エージェントに調査させる前提なら、命名を揃えること自体が投資になります。

もう一つは、命名規約をスキルの中で伝えておくことです。規約から外れた名前を使わざるを得ない場合や、既存リソースの名前を変えられない場合はこちらになります。前述のランブックに、こんな一節を足しておくイメージです。

## ロググループの命名

このリポジトリで管理している Lambda 関数のロググループは、CDK の自動命名に
任せているため `/aws/lambda/` 配下にはない。`DevOpsAgentIssueVerification-`
で始まる名前で作成されるため、ロググループを探すときはこのプレフィックスで
絞り込むこと。

調査のたびに探索させるより、一度書いておくほうが安いはずです。ランブックは Git で管理できるので、命名規約が変わったらそこを直すだけで済みます。

おわりに

AWS DevOps Agent に、障害の調査結果から GitHub Issue を起票させてみました。

ネイティブの GitHub 連携は読み取り専用なので、GitHub MCP サーバーを接続して issue_write を許可します。ツール名が create_issue ではない点だけ気をつければ、設定自体は AWS CDK で数行です。

起票された Issue の質は期待以上でした。根拠ログと根拠コードを両方引用し、修正方針も 1 行レベルまで具体的で、依頼していないエラーハンドリングの指摘まで付いてきました。

チャットだけでなくインシデントレスポンスの調査からも起票できます。手順をランブックとして Git に置いておけば、重複の扱いや本文の書式をチームの流儀に寄せられます。実際に、同じ事象の Issue が既にあるときは起票を見送り、ないときは新規に起票しました。

一番の落とし穴は GitHub App の権限でした。ツールを許可しても、App 側に Issues の書き込みがなければ 403 で止まります。起票が通らないときは、DevOps Agent 側と GitHub 側のどちらで止まっているのかを切り分けるところから始めるとよさそうです。

以上

この記事をシェアする

DevelopersIO 2026

関連記事