この記事では Claude / Xcode / Visual Studio Code の3つで Enter と Shift+Enter の役割を入れ替えます。所要時間は10分ほど、ターミナルはコピー&ペーストだけで完結します。
対象読者
- Enter 一発送信のUIで誤送信を繰り返している人
- アプリ側に「Enterで送信」トグルが無くて詰んでいる人
- macOS のキーカスタマイズを初めて触る人
仕組み
macOS には「特定のアプリでだけキーの意味を変える」標準機能がありません。そこで Karabiner-Elements(無料・オープンソースの定番キーカスタマイズツール)を使い、対象アプリが最前面にあるときだけキーを入れ替えます。
| 押すキー | 変換後 | 結果 |
|---|---|---|
| Enter | Shift+Enter | 改行 |
| Shift+Enter | Enter | 送信 |
対象アプリ以外では一切影響がありません。
ステップ1: Karabiner-Elements をインストール
Homebrew を使う場合:
brew install --cask karabiner-elements
使わない場合は 公式サイト から .dmg をダウンロードし、通常どおりインストールします。
ステップ2: 一度アプリを起動する
必ずここで一度 Karabiner-Elements.app を開いてください。
初回起動時に設定ファイル ~/.config/karabiner/karabiner.json が生成されます。これが無いと後のステップが失敗します。
ステップ3: システムの許可を与える
Karabiner はキー入力を横取りするため、macOS の許可が2か所必要です。ここを飛ばすと、設定を書き込んでもキーは入れ替わりません。
A. ドライバ機能拡張
システム設定 → 一般 → ログイン項目と機能拡張 → 一番下の ドライバ機能拡張 → Karabiner をオン
再起動を求められたら従ってください。
B. 入力監視
システム設定 → プライバシーとセキュリティ → 入力監視 → karabiner_grabber と karabiner_observer にチェック
確認
pgrep -fl karabiner_grabber
プロセスIDとパスが表示されれば稼働中です。何も出なければ A に戻ってください。
ステップ4: セットアップスクリプト
karabiner.json を直接編集してもいいのですが、JSONの階層が深くて手作業だと事故ります。バックアップと冪等性を持たせたスクリプトを用意しました。
#!/bin/bash
#
# Karabiner-Elements に「Enter=改行 / Shift+Enter=送信」ルールを適用します。
# 対象アプリ: Claude / Xcode / Visual Studio Code
#
# 実行: bash setup-enter-swap.sh
# 元に戻す: bash setup-enter-swap.sh --uninstall
#
set -euo pipefail
CONFIG="$HOME/.config/karabiner/karabiner.json"
RULE_DESC="Enter=改行, Shift+Enter=送信 (Claude/Xcode/VS Code)"
MODE="install"
[[ "${1:-}" == "--uninstall" ]] && MODE="uninstall"
echo "==> Enter/Shift+Enter 入れ替えセットアップ (${MODE})"
# 1. Karabiner-Elements の確認 / インストール
if [[ ! -d "/Applications/Karabiner-Elements.app" ]]; then
if [[ "$MODE" == "uninstall" ]]; then
echo "Karabiner-Elements が見つかりません。何もせず終了します。"
exit 0
fi
echo "==> Karabiner-Elements が未インストールです。"
if command -v brew >/dev/null 2>&1; then
echo " Homebrew でインストールします..."
brew install --cask karabiner-elements
else
echo " Homebrew がありません。以下から手動でインストールしてください:"
echo " https://karabiner-elements.pqrs.org/"
exit 1
fi
echo
echo " ※ 初回はシステム設定でドライバ/入力監視の許可が必要です。"
echo " Karabiner-Elements.app を一度起動し、許可を与えてから"
echo " このスクリプトを再実行してください。"
open -a "Karabiner-Elements" 2>/dev/null || true
exit 0
fi
# 2. 設定ファイルの確認
if [[ ! -f "$CONFIG" ]]; then
echo "設定ファイルが見つかりません: $CONFIG"
echo "Karabiner-Elements.app を一度起動してから再実行してください。"
open -a "Karabiner-Elements" 2>/dev/null || true
exit 1
fi
# 3. バックアップ
BACKUP="${CONFIG}.bak.$(date +%Y%m%d-%H%M%S)"
cp "$CONFIG" "$BACKUP"
echo "==> バックアップ: $BACKUP"
# 4. ルールの適用 / 削除
CONFIG="$CONFIG" RULE_DESC="$RULE_DESC" MODE="$MODE" python3 <<'PY'
import json, os, sys
config_path = os.environ["CONFIG"]
rule_desc = os.environ["RULE_DESC"]
mode = os.environ["MODE"]
BUNDLES = [
r"^com\.anthropic\.claudefordesktop$",
r"^com\.apple\.dt\.Xcode$",
r"^com\.microsoft\.VSCode$",
]
CONDITION = [{
"type": "frontmost_application_if",
"bundle_identifiers": BUNDLES,
}]
RULE = {
"description": rule_desc,
"manipulators": [
{ # Shift+Enter -> Enter (= 送信)
"type": "basic",
"from": {
"key_code": "return_or_enter",
"modifiers": {"mandatory": ["shift"]},
},
"to": [{"key_code": "return_or_enter"}],
"conditions": CONDITION,
},
{ # Enter -> Shift+Enter (= 改行)
"type": "basic",
"from": {
"key_code": "return_or_enter",
"modifiers": {"optional": []},
},
"to": [{"key_code": "return_or_enter",
"modifiers": ["left_shift"]}],
"conditions": CONDITION,
},
],
}
with open(config_path, encoding="utf-8") as f:
cfg = json.load(f)
profiles = cfg.get("profiles") or []
if not profiles:
print("プロファイルが見つかりません。", file=sys.stderr)
sys.exit(1)
# 選択中のプロファイル(なければ先頭)
target = next((p for p in profiles if p.get("selected")), profiles[0])
cm = target.setdefault("complex_modifications", {})
rules = cm.setdefault("rules", [])
# 同名ルールを一旦除去(再実行しても重複しない)
before = len(rules)
rules[:] = [r for r in rules if r.get("description") != rule_desc]
removed = before - len(rules)
if mode == "install":
rules.insert(0, RULE)
action = "更新しました" if removed else "追加しました"
else:
action = "削除しました" if removed else "見つかりませんでした(変更なし)"
with open(config_path, "w", encoding="utf-8") as f:
json.dump(cfg, f, indent=4, ensure_ascii=False)
f.write("\n")
print(f"==> プロファイル「{target.get('name', '?')}」のルールを{action}")
PY
# 5. 反映(Karabiner は karabiner.json の変更を自動検知して即反映)
if ! pgrep -q -f "Karabiner-Elements"; then
open -a "Karabiner-Elements" 2>/dev/null || true
fi
echo "==> 完了"
ポイントは3つです。
- 実行前に必ずタイムスタンプ付きでバックアップを取る
- 同名ルールを除去してから挿入するので、何度実行しても重複しない
- 既存の他のルールには一切触れない
保存したらターミナルで実行します。
bash ~/Downloads/setup-enter-swap.sh
Finder でダブルクリックしないでください。処理が一瞬で終わるためウインドウが即座に閉じ、A session ended very soon after starting という警告だけが残ってエラーが読めません。必ずターミナルから上記の形で実行します。
成功するとこう表示されます。
==> Enter/Shift+Enter 入れ替えセットアップ (install)
==> バックアップ: ~/.config/karabiner/karabiner.json.bak.20260719-113000
==> プロファイル「Default profile」のルールを追加しました
==> 完了
ステップ5: 確認
python3 -c "
import json,os
d=json.load(open(os.path.expanduser('~/.config/karabiner/karabiner.json')))
p=next((x for x in d['profiles'] if x.get('selected')), d['profiles'][0])
for r in p.get('complex_modifications',{}).get('rules',[]):
print('-', r.get('description'))
"
Enter=改行, Shift+Enter=送信 (Claude/Xcode/VS Code) が表示されれば完了です。
最後に Claude の入力欄で Enter を押し、送信されずに改行されることを確かめてください。
元に戻す
bash ~/Downloads/setup-enter-swap.sh --uninstall
追加したルールだけを削除します。Karabiner 自体や他の設定は残ります。
トラブルシューティング
「設定ファイルが見つかりません」と出る
ステップ2をまだ実行していません。Karabiner-Elements.app を一度起動してください。
A session ended very soon after starting と出た
ダブルクリック実行が原因です。ターミナルから bash ~/Downloads/setup-enter-swap.sh の形で実行し直してください。
cannot execute: Permission denied
chmod +x ~/Downloads/setup-enter-swap.sh
頭に bash を付けて実行すれば権限付与は不要です。
ダウンロードしたファイルがブロックされる
Gatekeeper の隔離属性を外します。
xattr -d com.apple.quarantine ~/Downloads/setup-enter-swap.sh
設定は入っているのにキーが変わらない
ほぼ確実にステップ3の許可漏れです。pgrep -fl karabiner_grabber で稼働を確認してください。
対象アプリを変えたい・増やしたい
スクリプト内の BUNDLES を編集します。
BUNDLES = [
r"^com\.anthropic\.claudefordesktop$",
r"^com\.apple\.dt\.Xcode$",
r"^com\.microsoft\.VSCode$",
]
バンドルIDは Karabiner-EventViewer の Frontmost Application タブで確認できます。対象アプリを最前面にすると実際の値が表示されるので、それをそのまま貼り付けてください。
注意点: アプリ単位でしか判定できない
Karabiner の条件判定は アプリ単位 で、ウインドウやペイン単位ではありません。
つまり Xcode や VS Code では、AIチャット欄だけでなくエディタ本体でも Enter が Shift+Enter として送られます。多くの場合は同じ「改行」として扱われるため実害はありませんが、拡張機能が Shift+Enter に別の動作を割り当てていると挙動が変わります。
エディタでの副作用を避けたい場合は、BUNDLES から Xcode / VS Code を外して Claude だけに絞り、各エディタ側のキーバインド設定(VS Code なら keybindings.json)で対応するのが安全です。
おわりに
誤送信は地味にストレスが溜まる上、相手にも中途半端な文章が届いてしまいます。一度設定してしまえば意識せず済むので、心当たりのある方はぜひ。