I tried setting up guardrails with hooks to prevent accidentally registering issues to the wrong project via the Backlog MCP Server

I tried setting up guardrails with hooks to prevent accidentally registering issues to the wrong project via the Backlog MCP Server

I tried setting up hooks to prevent incidents caused by unintended AI behavior. I'm using Backlog MCP Server's add_issue as the subject, but the concept can also be applied to other MCP Servers and tools.
2026.09.04

This page has been translated by machine translation. View original

This is Iwata from the Retail App Co-Creation Department @ Osaka.

Backlog MCP Server is a very convenient tool for operating Backlog from AI agents like Claude Code.

However, just because it's convenient doesn't mean you should use it without setting up appropriate guardrails — there's a risk of accidents caused by unintended AI behavior. In this blog post, I'll introduce an implementation sample that uses hooks as guardrails when using Backlog MCP Server, restricting operations to specific Backlog projects.

※ Going forward, the word "project" refers to the directory where Claude Code is being executed. It is used in the context of user-level settings/project-level settings, and does not refer to Claude's projects. Also, Backlog projects will be explicitly referred to as "Backlog projects."

Introduction

If there are multiple Backlog projects within a Backlog space connected via Backlog MCP Server, there is a concern that you might accidentally register an issue to Backlog project B when you intended to register it to Backlog project A. Even if you write something like "When using Backlog MCP Server, always specify Backlog project ID 12345!!!" in Claude.md, there's no guarantee it will be followed. To reliably prevent unintended behavior, deterministic protection through hooks is effective.

Let's Try It

With that, let's go ahead and register the hooks.

Thinking About the Design

First, I thought about which scope the hooks should be set at. Setting them at the project level raises concerns about missing hook configurations for newly added projects in the future, so it seems better to add them as user-level settings.

However, making them user-level settings means project-specific configurations can't be written, which requires the hooks to be generic. You can't hardcode Backlog project IDs directly into the shell script. On the other hand, since the Backlog projects whose issues you want to allow operations on should differ per project, I decided to list the allowed Backlog projects in a text file within the project, and have the script read this text file to determine whether a tool call is permitted.

For this implementation, I'll list the IDs of Backlog projects allowed for operations in a text file called backlog.allow.

Registering the PreToolUse Hook

First, add the hooks description to ~/.claude/settings.json as follows.

~/.claude/settings.json
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__backlog__add_issue",
        "hooks": [
          {
            "type": "command",
            "command": "${HOME}/.claude/hooks/backlog/project-check.sh || { echo 'hook script execution failed' 1>&2; exit 2; }"
          }
        ]
      }
    ]
  }

mcp__backlog__add_issue is specified for matcher. This assumes Backlog MCP Server is registered under the name backlog, and with this configuration, the hooks will fire when the add_issue tool of Backlog MCP Server is called.

project-check.sh is specified as a shell script for command. If this shell script doesn't exist, || { echo 'hook script execution failed' 1>&2; exit 2; is specified so that it exits with completion code 2 and the tool call is blocked.

Implementing the Shell Script

Next, let's implement project-check.sh specified in the hooks earlier. Here's what the content looks like.

~/.claude/hooks/backlog/project-check.sh
#!/bin/bash

PROJECT_ID=$(jq -r '.tool_input.projectId')
ALLOW_FILE="${CLAUDE_PROJECT_DIR}/.backlog.allow"

if [ ! -f "$ALLOW_FILE" ]; then
  jq -n "{
      hookSpecificOutput: {
        hookEventName: \"PreToolUse\",
        permissionDecision: \"deny\",
        permissionDecisionReason: \"Allow file not found: $ALLOW_FILE\"
      }
    }"
  exit 2
fi

if ! grep -Fxq "$PROJECT_ID" "$ALLOW_FILE"; then
  jq -n "{
      hookSpecificOutput: {
        hookEventName: \"PreToolUse\",
        permissionDecision: \"deny\",
        permissionDecisionReason: \"Project ID $PROJECT_ID not allowed\"
      }
    }"
  exit 2
fi

exit 0

First, PROJECT_ID=$(jq -r '.tool_input.projectId') retrieves the Backlog project ID from the tool call arguments.

In the subsequent processing, it checks whether a .backlog.allow file exists under the project directory, and if it doesn't exist, the tool call is denied. If the file exists, it checks whether the Backlog project ID specified in the tool arguments exists within .backlog.allow and determines whether the tool call is permitted.

Testing It Out

Now that the hooks are ready, let's test them. All tests below were conducted using the following prompt and sonnet5.

 Please register an issue using the mcp__backlog__add_issue tool
  Project ID is 826921
  Title is "test from claude code", issueTypeId is 4436305, priorityId is 2

First, the case where I forgot to grant execute permissions to the shell script...

Error when the shell script has no execute permission

It resulted in an error saying Issue registration is blocked because the hook script ~/.claude/hooks/backlog/project-check.sh does not have execute permission. and did not proceed with registering the issue.

Next, the result when the .backlog.allow text file does not exist.

Error when the configuration file does not exist

This also resulted in an error as expected.

Next, the result when the Backlog project ID specified in the prompt does not exist within .backlog.allow.

Error when the Backlog project is not allowed in the configuration file

Execution failed with Issue registration failed because access to project ID 826921 is not permitted ("Project ID 826921 not allowed" error).

Finally, the result when the Backlog project ID specified in the prompt exists within .backlog.allow.

When the Backlog project is allowed in the configuration file

Registration succeeded with the message Issue created. Registered to project ID 826921 as TEST-9 ("test from claude code", type: Task, priority: High). The issue was properly registered.

Registered Backlog issue

Summary

This time I only tried add_issue for now, but for delete_issue and update_issue as well, by implementing hooks that deny tool calls by issueId while checking the issueKey prefix, it seems possible to implement guardrails that allow issue operations only for specific Backlog projects.

※ I regretted that the design of listing Backlog project IDs in a text file has poor extensibility — I should have stored the configuration in JSON format.

Another issue is that if the MCP server is not registered under the name backlog, the tool name won't become mcp__backlog__add_issue, and it won't match the hooks matcher...

References


Claudeならクラスメソッドにお任せください

クラスメソッドは、Anthropic社とリセラー契約を締結しています。各種製品ガイドから、業種別の活用法、フェーズごとのお悩み解決などサービス支援ページにまとめております。まずはご覧いただき、お気軽にご相談ください。

サービス詳細を見る

Share this article

AI白書