丁寧な暮らしをしたいブログ

ITと日々の暮らしについて書きます。

奈良から茅ヶ崎に引っ越して、暮らしの成り立ちの違いに気づいた

3月に奈良から茅ヶ崎に引っ越してきた。

奈良と茅ヶ崎での暮らしの違いについて、感じていることを書いてみたい。

どうして茅ヶ崎に引っ越したのか

fuyu.hatenablog.com

きっかけは、私が転職活動をすることになったことだ。最近はフルリモート可の求人が限られるため、首都圏に住みたい気持ちがあった。

また、妻の勤務先である奈良の中小企業でも、社長に東京拠点を本格運用したい意向があり、夫婦そろって東京方面で暮らしたい状況が、ちょうどよいタイミングで重なった。

首都圏の中から神奈川県の茅ヶ崎市を選んだ理由としては、

  • 私の実家が隣の平塚市にあり、湘南地域になじみがあったこと
  • 不動産屋の担当者がサーファーで、自分のインスタを見せながら茅ヶ崎を猛プッシュしてきたこと
  • 実際に現地に降りてみたら、飲食店や個人商店が充実する様子に、奈良に近いものを感じて好ましく思ったこと

このあたりが大きい。

実際に住んでみると、生活圏の成り立ちがかなり違った

実際に住み始めてみると、奈良と茅ヶ崎では生活圏の成り立ちがかなり違うことに気づいた。

奈良で住んでいた近鉄奈良駅の周辺は、商店街や個人商店が生活を支えていた。近鉄奈良駅もJR奈良駅も、駅周辺には商業施設らしい商業施設がほとんどなかった。

一方で茅ヶ崎は、駅前の複合商業施設の存在感がかなり大きい。駅ビルのラスカや、少し歩いた先にあるイオン系の複合商業施設である「そよら湘南茅ヶ崎」に、ユニクロや無印良品、成城石井、カインズ、イオンシネマのような便利なテナントが揃っている。

どちらも駅周辺が充実している街ではあるのだけれど、その暮らしやすさの作られ方は異なる。奈良は伝統的な個人商店中心の世界で、茅ヶ崎はイオン的な複合商業施設中心の世界に近い。

徒歩圏が充実しすぎていて自堕落な奈良

奈良では、近鉄奈良駅とJR奈良駅のちょうど真ん中の一等地に住んでいた。生活は徒歩圏で高度に完結していた。

近鉄奈良駅とJR奈良駅の周辺はチェーン飲食店も充実しており*1、リモートワークで猛烈に仕事をして、近くの餃子の王将などで晩ごはんを食べる日々が続いた。

休日には、近鉄奈良駅前の商店街や、ならまちのあたりの文化的なエリアを散歩すると、それだけで満足してしまうところがあった。

もともとは斑鳩、天理、明日香、吉野などの奈良市以外の地域にも魅力を感じて奈良に移住したはずなのに、実際には徒歩圏からほとんど出ないまま5年があっという間に過ぎてしまった。

フルリモート勤務で自堕落な生活を続けたことで、体力もかなり落ちてしまったように感じる。

茅ヶ崎では丁寧な暮らしを始めたい

茅ヶ崎では、駅から徒歩25分ほどの場所に住んでいる。自宅の周辺は奈良にいた頃と比べるとだいぶ不便だ。

茅ヶ崎では、その不便さを逆に活かして、丁寧な暮らしを始めたいと思っている。

まず、自転車を買った。

少し遠い駅前まで運動がてら移動して、無印良品のような店で生活を整えるものを買い揃える。コンビニやチェーン飲食店に頼りきらず、自炊も少しずつ増やしていく。そんな暮らし方をイメージしている。

首都圏のつらいところ

茅ヶ崎のような関東の地方都市を含む、首都圏の居住地のつらさも見えてきた。

まず、家賃が高い。奈良では、駅近の新築で広い賃貸を安く借りられた。茅ヶ崎では、駅から遠く、奈良の頃よりも狭い部屋を借りるのに、3万円以上高い賃料を払っている。

また、茅ヶ崎のような比較的落ち着いたところに住もうとすると、東京から遠くなる。今度の勤務先の新宿の職場に通うのに、トータルで片道2時間近くかかりそうで、とても大変な気がしている。

地方都市のポテンシャルを活かせないのはもったいない

今回、奈良から茅ヶ崎に引っ越してみてあらためて思ったのは、日本は地方都市のポテンシャルを活かせていないのではないか、ということだ。

奈良のように、文化的な独自性と暮らしやすさを両立している日本の地方都市は数多くあると思うが、仕事が東京に一極集中しているせいで、そういった魅力的な都市に住む選択肢を持てないことがある。

茅ヶ崎に住んでみて、便利で活気のある街を好ましく感じる一方で、今の自分には選択し得ない地方の魅力的な都市の様子を想像すると、なんとも言えないもったいない気持ちになってしまうのだ。

*1:松屋、松のや、餃子の王将、やよい軒、吉野家、すき家、サイゼリヤ、どうとんぼり神座、モスバーガー、鳥貴族など。

転職活動の記録と考えたこと

2026年の1月始めから2月半ばにかけて転職活動をしていたので、考えていたことを振り返ってみたい。

転職を考えたきっかけ

約5年勤めていた現職で、私は新規プロダクトの開発を中心に担う遊撃隊的な部署で、チームリーダーをしていた。

一方で会社としては、事業拡大に伴って、プロダクト全体を3つの役割に分け、共通基盤システムを介して全社的に連携していく構想が進んでいた。

昨年、この構想をさらに推し進めるかたちで、いわゆる「逆コンウェイの法則」の考え方で、開発部署を3層に整理する組織再編が実施された。その結果、私の所属部署も、プロダクトとメンバーが綺麗に3つに分かれ、既存部署に統合されてしまった。

この組織分割によって、開発者としてビジネスやユーザー体験に関われる範囲が狭まり、自分が仕事を通して実現したい価値提供から遠ざかると感じたため、転職を検討するようになった。

今回の転職で重視したこと

今回の転職では、ユーザーからのフィードバックに近い距離で開発できることと、現場で開発を推進できることを重視していた。

現職では、組織の拡大にともなって部署間のコミュニケーションがボトルネックになりつつあり、そこにやりづらさを感じていたため、応募先は自然と小さめの企業が中心になった。

また、カジュアル面談を通じて、製造業DXの領域には勢いのあるベンチャー企業が多く、今かなり活発な市場になっていることが見えてきた。工場などの現場改善を通じて、ユーザーからのフィードバックに近い距離で開発できる点も、自分が重視していたことと重なっていたため、製造業DXに関わる求人を多く受けることになった。

転職活動の経過

今回の転職活動は短期で進めたかったので、転職エージェント2人とWantedlyでの自己応募という複数の経路を使って、一気に進めた。

年始から動き始め、毎週かなりの数の面接を入れながら進めた結果、ほぼ最短の1か月半で4社の内定が揃った。

今回は小さめの企業を中心に受けていたが、オファー面談まで進んでみると、特に規模の小さいスタートアップでは、仕事そのものは面白そうでも、人事労務制度が未整備で、入社後のリスクを読みきれないと感じる部分もあった。

そのため、最終的には、仕事の面白さを感じつつも、一定の規模があり、人事労務制度もある程度整っている会社を選ぶことにした。

年収は現職より100万円上がって800万円台になった。

エンジニアのキャリアは「やりたいことベース」でもよいのかもしれない

エンジニアのキャリアについて、スペシャリストとマネジメントのどちらを目指すか、みたいなテーマは誰にとっても悩ましいと思う。ただ、今回の転職で思ったのは、あまり「型」にはめて考えずに、自分の得意なこと・やりたいことベースでも意外に通用する、ということだ。

Webベンチャーのリーダー層に求められるのは、結局のところ「フワッとおまかせでいい感じに」みたいなところにつきるので、これまでの経験を活かして、技術・人・組織に謙虚に向き合うところに、おのずと道が開かれるのだと思う。

現職ではかなり仕事に打ち込んできて、年齢を重ねたことも影響しているのか、何だか根拠もなく自信があるようなところもある。新しい職場で何ができるのか、今から楽しみだ。

Rails 8.1 で schema.rb が ABC 順になったので structure.sql に移行してみた

いつものように Dependabot の自動アップデートで Rails 8.1 に上げたところ、急に schema.rb に不可解な差分が出るようになって驚いた。

差分をよく見ると、テーブルのカラムが ABC 順に並び替えられていることに気づく。

railsguides.jp

Active Recordは、schema.rb内のテーブルカラムをデフォルトでアルファベット順にソートするようになりました。これにより、マシン間でスキーマダンプが一貫するようになり、マイグレーションの順序によって左右されなくなり、結果としてノイズの多い差分が削減されます。structure.sqlは、カラム順序を厳密に維持するために引き続き利用できます。スキーマ変更のアルファベット順化の詳細については、#53281を参照してください。

Railsガイドで、8.1 で追加された仕様変更として解説されている。

github.com

さらに詳しい経緯は、該当の PR を読むと把握できる。

当初は「オプションで選べるようにする」機能として PR が立てられた。@byroot によって、オプションにすべきかどうかに疑問が投げかけられる。@matthewd は schema.rb でカラムの並び順を参照するユースケースについて言及するなどして、慎重な見解を述べている。

そんな中、DHH の鶴の一声で「オプションにはせず、常に ABC 順にする」という方針が決定される。

This should not be an option. This should just be default behavior. Schema dumping should be deterministic regardless of the platform.

これはオプションにするべきではない。これは単にデフォルトの挙動であるべきだ。スキーマのダンプは、プラットフォームによらず決定的であるべきだ。

Rails における DHH の、神のような決定権を感じさせる一幕だ。3つの文のすべてに should が入っていて、「そうあるべきだ」という強い意志がにじみ出ている。

方針決定後の @matthewd によるフォローのコメントでは、カラムの並び順を管理したい場合は structure.sql を使うべきであることが示唆されている。

変更前のschema.rbの課題

変更前の schema.rb の仕様では、複数の開発者が同時に DB マイグレーションを実装するような場合に問題があった。各開発者がローカル環境でどのような順番でマイグレーションを実行したかによって、カラムの並び順が変わってしまう可能性があったのだ。

そのため「 rails db:migrate:reset コマンドを実行した場合の並び順を正とする」といった運用ルールを決めておかないと、カラムの並び順が一意に定まるようにファイルを更新することができなかった。

この問題に対して、カラムを ABC 順に並べることで、常に一意の並び順に決定できるようにしたのが、この PR による仕様変更だ。

とはいえ、カラムの並び順は重要では?

その一方で、MySQL にはカラム追加の際に任意の並び順を指定できる機能があるため、可読性の観点などからカラムの並び順を意識的に管理しているケースもあるだろう。私のチームも、カラムの並び順を意識する運用にしていた。

カラムの並び順を意識する開発方針の場合、コードレビューでは「カラムの並び順が妥当かどうか」も確認対象になる。このとき、カラムの並び順の差分を確認できるファイルが存在しないと、レビュワーは手元で DBMS を動かしてスキーマを確認しなければならない。これは手間がかかるし、チームとして実装・コードレビューの運用方針を徹底することも難しくなりそうだ。

そのため、カラムの並び順を意識する開発方針を取るのであれば、「実際のカラムの並び順を参照できるファイルがあること」は、運用上ほぼ必須要件になりそうだ。

こうした背景から、私のチームでは schema.rb から structure.sql へスキーマダンプのフォーマットを移行することにした。

structure.sqlとは

config.active_record.schema_format = :sql

config.active_record.schema_format:sql に設定すると、デフォルトの schema.rb ではなく、structure.sql によってスキーマダンプが管理されるようになる。

structure.sql では、データベース固有のツールを用いてデータベースの構造がダンプされる。たとえば MySQL の場合は、mysqldump ユーティリティ が用いられる。

structure.sql の中身は、以下の部分例のように「そのまま SQL」となる。

/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `announcements`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `announcements` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `site_id` bigint NOT NULL,
  `title` varchar(255) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT 'タイトル',
  `content` text COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '内容',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_announcements_on_site_id` (`site_id`),
  CONSTRAINT `fk_rails_81bca04b30` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='お知らせ';

structure.sql に含まれる SQL を実行することで、定義されたテーブル構造を再現できる仕組みになっている。

structure.sql では環境ごとの差異をなくすために工夫が必要

実際に試してみると、 structure.sql は、開発環境ごとの差異をなくして、純粋にスキーマの差分だけを管理できるようにするために、工夫しなければいけないことがいくつかあると気づいた。

mysqldump を実行するクライアントの違い

FROM ruby:3.4.7

RUN apt-get update \
  && apt-get install -y --no-install-recommends default-mysql-client \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

例えば、上のように ruby の Docker イメージをベースに環境構築した場合、OS は Debian になるため、MySQL 互換の実装として MariaDB が標準になっている。Debian の APT の初期状態では、Oracle MySQL の mysql-client はインストールできず、default-mysql-client の実態は mariadb-client となっている。

この環境では、mysqldump コマンドは mariadb-dump へのシンボリックリンクとして提供される。

mysqldump コマンドの実態が本物の mysqldump なのか、mariadb-dump へのシンボリックリンクなのかによって、利用できるオプションやスキーマダンプの生成結果に差異が生じることに注意が必要だ。

AUTO_INCREMENT= の値が開発環境ごとにばらばらになる

CREATE TABLE `announcements` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `site_id` bigint NOT NULL,
  `title` varchar(255) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT 'タイトル',
  `content` text COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '内容',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_announcements_on_site_id` (`site_id`),
  CONSTRAINT `fk_rails_81bca04b30` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='お知らせ';

mysqldump の出力結果では、上の AUTO_INCREMENT=6 のように、開発環境のデータがどう入っているかによって「次に INSERT されるプライマリキーの採番値」に差異が生じてしまう。

スキーマロードするかマイグレーションするかで CHARACTER SET の有無が異なる

`title` varchar(255) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT 'タイトル',
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT 'タイトル',

上のように、varchar カラムに対する CHARACTER SET utf8mb4 の有無が、Rails でスキーマロードするか、マイグレーションするかによって異なる問題に遭遇した。

zenn.dev

これは mysqldump の内部実装による複雑な事象だが、上の記事で詳しく解説してくださっている方がいたおかげで何とか理解できた。

  • 照合順序(Collation)をデフォルトから変更した場合に、この差異が生じる
  • 差分の有無による実際の DB スキーマの仕様の違いは存在しない
  • structure.sql の SQL を実行した場合と、Rails の通常のマイグレーションで実行される SQL が異なる
    • structure.sql の SQL ではカラムごとに COLLATE が明示されているが、Rails のマイグレーションでは明示されない
  • Rails では DB マイグレーション系のコマンドの実行方法によって、 rails db:schema:load が実行され、 structure.sql の SQL が実行される場合がある
  • この違いによって mysqldump の出力結果に差異が生じる

環境差異の解消方法

structure.sql を生成する際の mysqldump の挙動をカスタマイズする方法は、どのレイヤーで上書きするかによっていくつかの選択肢がありそうだ。私はその中から、config/initializers/mysql_database_tasks.rb に以下のようなファイルを配置して設定を上書きする方法を選んだ。

# development と test 環境でのみ設定する
if Rails.env.local?
  # mysqldump で structure.sql を生成する方法を設定する
  ActiveSupport.on_load(:active_record) do
    # mysqldump 実行時に TLS 接続をスキップする
    # 開発環境ではシンボリックリンクで mariadb-dump が実行されるため、 --ssl-mode オプションが存在しない
    # GitHub Actions の環境では mysqldump が実行される
    mysqldump_flags = ENV['GITHUB_ACTIONS'] == 'true' ? %w[--ssl-mode=DISABLED] : %w[--skip-ssl]

    ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = {
      mysql2: mysqldump_flags
    }

    ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = {
      mysql2: mysqldump_flags
    }

    ActiveRecord::Tasks::MySQLDatabaseTasks.prepend(Module.new do
      def structure_dump(filename, extra_flags)
        super
        content = File.read(filename)
        # AUTO_INCREMENT= の値は個別の開発者の環境のデータの入り方によって異なるため、 structure.sql から削除する
        content.gsub!(/\sAUTO_INCREMENT=\d+/, '')

        # DDL で varchar カラムに COLLATE を指定するかによって structure.sql に `CHARACTER SET utf8mb4` の有無の差異が生じる
        # Rails のマイグレーションでは COLLATE指定なし、 structure.sql をロードした場合は COLLATE 指定ありの DDL になる
        # `CHARACTER SET utf8mb4` の有無による仕様の違いは存在しないため、開発環境ごとの差異をなくす目的で、structure.sql から削除する
        content.gsub!(' CHARACTER SET utf8mb4', '')
        File.write(filename, content)
      end
    end)
  end
end
# mysqldump 実行時に TLS 接続をスキップする
# 開発環境ではシンボリックリンクで mariadb-dump が実行されるため、 --ssl-mode オプションが存在しない
# GitHub Actions の環境では mysqldump が実行される
mysqldump_flags = ENV['GITHUB_ACTIONS'] == 'true' ? %w[--ssl-mode=DISABLED] : %w[--skip-ssl]

この部分では、開発環境で実行される mysqldump の実態が mariadb-dump となっている一方で、GitHub Actions のUbuntu環境では mysqldump となっている問題に対処している。

content = File.read(filename)
# AUTO_INCREMENT= の値は個別の開発者の環境のデータの入り方によって異なるため、 structure.sql から削除する
content.gsub!(/\sAUTO_INCREMENT=\d+/, '')

# DDL で varchar カラムに COLLATE を指定するかによって structure.sql に `CHARACTER SET utf8mb4` の有無の差異が生じる
# Rails のマイグレーションでは COLLATE指定なし、 structure.sql をロードした場合は COLLATE 指定ありの DDL になる
# `CHARACTER SET utf8mb4` の有無による仕様の違いは存在しないため、開発環境ごとの差異をなくす目的で、structure.sql から削除する
content.gsub!(' CHARACTER SET utf8mb4', '')
File.write(filename, content)

この部分では、正規表現の置換を用いて、開発環境による差異が出る部分を削除している。

文字列の置換ではなく、 mysqldump のオプション指定によって解消できないかも検討したが、私の試した範囲では難しかった。インターネットで事例を検索しても、愚直に文字列を置換しているソリューションしか見つけられなかった。

この設定をすることによって、環境ごとの差異がなく、スキーマの差分のみを綺麗に確認できる structure.sql の運用が可能になった。

structure.sql を使ってみた所感

その後、structure.sql を運用してみて、実際の SQL が表示されることで MySQL 上の設定に自覚的になれることにはメリットがありそうだ。

一方で、structure.sql に書かれる SQL は情報量が多く、ごちゃごちゃしていて読みづらいのも事実だ。「schema.rb でサッとテーブル情報を確認できたのは、やっぱり良い体験だったなあ」という思いは正直なところある。

トレードオフのある論点について、ライブラリの意思決定の難しさを垣間見ることができたと感じている。

git worktree を使いこなして、AI コーディングエージェントの同時並行開発を実現する

OpenAI Codex に GPT-5-Codex が登場してから、ローカル環境で動かせる AI コーディングエージェントが、業務でも普通に使えるレベルに到達したと感じている。最近では、実装は自分で一から書くのではなく、コーディングエージェントに任せて、最後の手直しだけを自分でやることが多い。

ローカル環境でのコーディングエージェントの活用が業務の中心になってくると、ひとつ気になる制約が見えてくる。「ある Git リポジトリをコーディングエージェントが編集しているあいだに、そのリポジトリを同時に編集しづらい」という問題だ。

具体的には、次のようなことをやりたくなる。

  • 同じ Git リポジトリに対して、複数のコーディングエージェントを同時に動かしたい
    • チケット A とチケット B の開発を同時に進める
    • Codex と Claude Code に同じチケットの開発を依頼して、アウトプットの品質が良い方を採用する
  • 同じリモートブランチに対して、コーディングエージェントと人間で同時に開発したい

しかし Git では、ひとつの作業ディレクトリにつき同時にチェックアウトできるブランチは 1 つだけだ。普通に開発ブランチを切って作業している場合、そのリポジトリをコーディングエージェントが編集しているあいだに、同時に編集しようとすると、差分が混ざってしまい、訳のわからない状態になってしまう。

こういった問題を解決する存在として、2015 年にリリースされた Git 2.5 で導入された git worktree の機能が、コーディングエージェントを用いた複数同時開発の文脈で、改めて注目されている。

git worktree は、ひとつのリポジトリに対して複数の作業ディレクトリ(worktree)を作成できる機能だ。作業ディレクトリごとに別々のブランチをチェックアウトしておけば、それぞれの作業ディレクトリで、他の作業ディレクトリの編集内容に影響されることなく開発を進めることができる。

Claude Code のドキュメントでも、git worktree を使った並行開発の手法が紹介されている。

仕組みだけを見ると、「作業ディレクトリを増やして、それぞれでコーディングエージェントを動かすだけ」の簡単な話に見える。手元の開発環境で早速やってみようとなったのだが、実際に試してみると、いくつかハマりどころがあった。

試行錯誤の結果、快適に複数同時開発を回せる開発環境が整ったので、git worktree の使い方の一例として紹介したい。

この記事では、git worktree で作成されるディレクトリのことを「作業ディレクトリ」(worktreeに相当)と呼ぶ。

git worktree を初めて使ったときに困ったポイント

初めて git worktree の機能を試したときに、次のような部分が期待通りに動かなくて困った。

  • 作業ディレクトリの新規作成からコーディングエージェントに依頼すると、コーディングエージェントは作成した作業ディレクトリに移動して作業できない
    • コーディングエージェントに「起動時のカレントディレクトリ配下だけ編集可能」という制約がある場合に、それ以外のディレクトリに移動して編集することができない
  • .gitignore で指定された .env など、開発環境の動作に必要なファイルが、作成した作業ディレクトリ側には存在しない

今振り返ると、これらの問題は作業ディレクトリの新規作成からコーディングエージェントに任せようとしたことが原因だったと思う。作業ディレクトリの作成や .env の準備といった環境づくりは、自分の手で整えるのが良さそうだ。

とはいえ、開発チケットごとに毎回手作業が発生するのは面倒だ。そこで、私は「複数のチケット開発に使い回せる作業ディレクトリ」を作っておき、そこでコーディングエージェントに開発を依頼する運用にしている。以下、その作成手順を紹介したい。

コーディングエージェントを動かすための作業ディレクトリの作成手順

前提情報

Git リポジトリのデフォルトの作業ディレクトリを「人間が開発するための作業ディレクトリ」として扱い、コーディングエージェント用に新しい作業ディレクトリを追加で作成する、という想定での手順を紹介する。

以下はサンプルとして使う名称なので、実際の環境での名称に適宜置き換えてほしい。

作業ディレクトリの作成

まず、コーディングエージェント用の作業ディレクトリを git worktree で作成する。

git worktree add ../myapp-ai -b main-ai origin/main

このコマンドは、次の 2 つを同時に行っている。

  • ../myapp-ai という作業ディレクトリ(worktree)を新規に作成する
  • リモートのデフォルトブランチ(origin/main)をトラッキングする main-ai というローカルブランチを作成し、その作業ディレクトリでチェックアウトする

Git では、同じブランチを複数の作業ディレクトリで同時にチェックアウトすることはできない。そのため、デフォルトブランチと同じ名前のブランチ(main)をそのまま複数の作業ディレクトリで共有することはできないので、main-ai のように、「デフォルトブランチをトラッキングするブランチ」を作業ディレクトリごとに用意する構成にしている。

作業ディレクトリごとに「デフォルトブランチをトラッキングするブランチ」を用意しておくと、どの作業ディレクトリでも同じように、

  • デフォルトブランチ相当のブランチで git pull して最新の実装内容を同期する
  • そこから新しく開発ブランチを切って作業を始める

という開発サイクルを回せるようになる。

.gitignore されている開発環境用のファイルを整える

次に、作成した作業ディレクトリ側に、開発環境で必要なファイルを用意する。

具体的には、.env のように .gitignore で ignore されていて、かつ開発環境の動作に必要なファイルは、コーディングエージェント用の作業ディレクトリにも配置しておく必要がある。

作成した作業ディレクトリでコーディングエージェントに開発依頼する

ここまで準備できたら、myapp-ai 側をコーディングエージェント用の作業ディレクトリとして使えるようになる。

  • 人間は従来どおり myapp 側で開発する
  • コーディングエージェントは myapp-ai 側で動かす

という分担にしておくと、お互いの作業が干渉しにくくなる。

同じ手順で作業ディレクトリを増やしていけば、コーディングエージェントを 2 つ 3 つと同時に動かせるようになる。

人間とコーディングエージェントで同じリモートブランチを参照する方法

コーディングエージェントが feature/example-function というリモートブランチを作成して push したとする。このブランチを、エージェントに編集してもらいつつ、人間もローカルで編集したい、という場面がある。

その場合は、次のようにして「同じリモートブランチをトラッキングする別名のローカルブランチ」を作成すると、運用がやりやすい。

git switch -c feature/example-function-human origin/feature/example-function

このように、リモートブランチをトラッキングするローカルブランチを人間用に別名で作成しておくと、

  • リモート側では 1 本のブランチ( feature/example-function
  • 手元では、エージェント用と人間用で別々のローカルブランチ

という構成にできる。これによって、人間とコーディングエージェントで同時に同じリモートブランチに対して編集することが可能になる。

Docker Composeによる開発環境構築で困ったポイントと解決策

Docker Compose を使って開発環境を構築している場合に、worktree ごとに別プロジェクト扱いになる、という問題にもぶつかった。

Docker Compose は、デフォルトでは「ディレクトリ名」をもとにプロジェクト名を決める。そのため、 myappmyapp-ai のようにディレクトリ名が異なると、

  • コンテナ名
  • ネットワーク名
  • ボリューム名

などがそれぞれ別々に作成されてしまう。

この仕様によって、

  • 複数の作業ディレクトリで同じポートを使おうとしてエラーになる
  • 作業ディレクトリごとに Docker リソースがどんどん増えていって整理しづらくなる

といった問題が発生した。

この問題については、COMPOSE_PROJECT_NAME環境変数.env に追加することで解決できた。

COMPOSE_PROJECT_NAME=myapp

また、コマンド実行時に -p--project-name ) オプションで明示的にプロジェクト名を指定することもできる。

docker compose -p myapp run --rm app bin/rspec

こうしてプロジェクト名を固定しておくと、複数の作業ディレクトリから、同じ Docker コンテナを共有できるようになり、Docker Composeによるコマンド実行が複数同時開発時にも支障なく動作するようになった。

VSCode での git worktree のサポート

VSCode では、git worktree の機能が公式にサポートされている。独立したリポジトリを扱うような感覚で、それぞれの作業ディレクトリを別々の VSCode ウィンドウで開くことができて便利だ。

おわりに

この記事に書いた手順で、ローカル環境でのコーディングエージェントによる同時並行開発を、快適に回せるようになった。git worktree は慣れるまでに挙動が非直感的に感じられる部分もあるが、一度コツを掴んでしまえば、AI 時代の開発に必須のテクニックという印象がある。

最近はモニターを 1 枚新たに買い足して、複数のコーディングエージェントがそれぞれの作業ディレクトリで頑張って開発している様子を小脇に眺められるようにしている。仕事の風景は、ほんの半年前と比べてもすっかり様変わりしていて、時代の急速な変化を日々感じているところだ。

GitHub CLIとAGENTS.mdで「自律駆動のAIエンジニア」をローカル環境で実現する

2025年の9月に、GPT‑5-Codexが登場してから、OpenAI Codexがすごく便利だ。

It’s more steerable, adheres better to AGENTS.md instructions, and produces higher-quality code—just tell it what you need without writing long instructions on style or code cleanliness.

GPT‑5-Codexはより指示に従いやすく、AGENTS.md のガイドに忠実で、高品質なコードを出力します—スタイルやコードのクリーンさに関する冗長な指示を書かずとも「必要なこと」を伝えれば動きます。

OpenAIがこう述べているように、従来のコーディングエージェントとは異なり、AIが暴走しないように細かい指示や工夫をせずとも、簡単な依頼で高度な実装ができるようになっている。

この性能で、月額20ドルのChatGPT Plusプランでも利用できるのだから驚きだ。

最近は、仕事でも個人開発でも、Codexを使い倒している。

この記事では、私が最近ハマっている、CodexにGitHub CLIを使わせてGitHubと連携する開発手法について書きたい。手法としては、Codex以外のコーディングエージェントに対しても応用可能だと思う。

AIに人間のように自律的に開発してもらいたい気持ち

仕事でもプライベートでも、やることが非常に多く、忙しい中で、AIには、人間に仕事を依頼するときと同じように、できるだけ丸投げに近い形で、自律的にタスクをこなしてもらいたい気持ちがある。

その点で、私はDevinのユーザー体験が好きだ。Devinは「完全自律型のAIエンジニア」を標榜している、クラウドサーバーで動作するタイプのツールで、GitHubのIssueを指定して依頼すると、PR作成まで全自動で進めてくれるような機能性になっている。

CodexにもDevinのようにクラウドサーバーに開発環境をセットアップする機能が存在するが、Devinとは異なり、Docker Composeを使えないなど、実用面で今ひとつ機能が足りない印象がある。現在のCodexは、手元のローカル環境で動かす使い方がメインになるだろう。

その一方で、ローカル環境で動かすツールだとしても、GitHub CLIを使えば、人間のようにGitHubを読み書きする自律的な開発が可能になると気づいた。

以下はGitHub CLIがインストールされていて、ghコマンドが動作する環境を前提とした説明になる。

ghコマンドを活用した依頼

ghコマンドを使ってissue 123の実装をしてください

ghコマンドが動作する環境では、デフォルト設定のCodexでも、上のように依頼すれば、GitHubと連携した開発が可能になる。

これだけでも便利だが、設定ファイルのAGENTS.mdに、依頼に使えるプロンプトと作業内容を定義することで、よりまとまった量の作業を簡単に依頼できるようになる。

AGENTS.mdでのプロンプト定義

## 作業依頼のプロンプト

コマンドのようなプロンプトで、一連の作業を簡単に依頼できるようにします。

シェルコマンドのように実行できることを期待しています。

### develop issue <ISSUE_NUMBER>

- ghコマンドで指定された<ISSUE_NUMBER>のissueの内容を確認します
- 開発用のGitブランチを新しく作成します
- 開発します

### create pr

- ghコマンドでprを作成します

### review pr <PR_NUMBER>

- ghコマンドで<PR_NUMBER>のprのコードレビューをします

AGENTS.mdにこのように書いておくと、 コーディングエージェントに依頼する際に、以下のようなプロンプトが動作するようになる。

# #123のIssueを開発する
develop issue 123

# PRを作成する
create pr

# #123のPRをコードレビューする
review pr 123

# &&でプロンプトをつないでもいい感じに解釈してくれる
# #123のIssueを開発してPRを作成する
develop issue 123 && create pr

GitHub利用に関する運用ルールの追加

ただし、これだけでは現場の開発ルールに従ったアウトプットができないので、以下のように、GitHub利用に関する運用ルールをAGENTS.mdに追加する。

## Git運用

- `feature/` から始めるブランチ名とします
- コミットメッセージはConventional Commitsのスタイルとします
- コミットは無理に1つにまとめようとせずに、変更の趣旨が分かりやすいように作成します

## GitHub PR作成

GitHubのPR作成を指示された場合は、以下の方針でPRを作成します。

- タイトルは開発対象のIssueと同じとします
- PRの説明を日本語で記載します
- `Resolves` の記法を用いて開発対象のIssueと紐づけます
- assigneeに実装者を指定します

開発環境セットアップ・構文チェック・テスト実行のコマンド整備

また、開発環境のセットアップや構文チェック、テスト実行のコマンド群も記載しておくと、AIが自律的に自らのコードの正しさを検証できるようになる。

例えばRailsアプリケーションをDocker Composeで動かす場合は、以下のような記載になるだろう。

## 動作環境

Docker(docker)とGitHub CLI(gh)のコマンドを実行できることを前提としています。

## コマンドリスト

- Install: `docker compose run --rm app bundle install`
- Migrate: `docker compose run --rm app bin/rails db:migrate`
- Lint: `docker compose run --rm app bin/rubocop`
- Test: `docker compose run --rm app bin/rspec`

AGENTS.mdの全容

ここまでの内容をまとめると、以下のようなAGENTS.mdファイルになる。

# AIコーディングエージェント向けのガイドライン

AIコーディングエージェント向けのガイドラインを記載します。

## 動作環境

Docker(docker)とGitHub CLI(gh)のコマンドを実行できることを前提としています。

## コマンドリスト

- Install: `docker compose run --rm app bundle install`
- Migrate: `docker compose run --rm app bin/rails db:migrate`
- Lint: `docker compose run --rm app bin/rubocop`
- Test: `docker compose run --rm app bin/rspec`

## Git運用

- `feature/` から始めるブランチ名とします
- コミットメッセージはConventional Commitsのスタイルとします
- コミットは無理に1つにまとめようとせずに、変更の趣旨が分かりやすいように作成します

## GitHub PR作成

GitHubのPR作成を指示された場合は、以下の方針でPRを作成します。

- タイトルは開発対象のIssueと同じとします
- PRの説明を日本語で記載します
- `Resolves` の記法を用いて開発対象のIssueと紐づけます
- assigneeに実装者を指定します

## 作業依頼のプロンプト

コマンドのようなプロンプトで、一連の作業を簡単に依頼できるようにします。

以下のように、シェルコマンドのように実行できることを期待しています。

```text
# #123のIssueを開発する
develop issue 123

# #123のIssueを開発してPRを作成する
develop issue 123 && create pr
```

### develop issue <ISSUE_NUMBER>

- ghコマンドで指定された<ISSUE_NUMBER>のissueの内容を確認します
- 開発用のGitブランチを新しく作成します
- 開発します

### create pr

- ghコマンドでprを作成します

### review pr <PR_NUMBER>

- ghコマンドで<PR_NUMBER>のprのコードレビューをします

まとめ

このような設定をすることで、GitHubのIssueに開発チケットを起票してAIに依頼するだけで、開発がドンドン進む環境が手に入る。もう昔の開発スタイルには戻れないな、という感じがしている。

短歌投稿サイトUtakataのMCPサーバーを作ってみた

最近Web開発業界で、「MCPサーバー」というものが注目されている。自社で管理している任意の情報システムと生成AIツールを連携できるような仕組みとして、活用の可能性が模索されているようだ。

簡単に実装できそうな様子があったので、まずはプライベートで実験してみたいと思って、自作の短歌投稿サイトUtakataと連携するMCPサーバーを作ってみた。

MCPサーバーの構築に興味はあるが、やり方がよく分からないような方の参考になるように、作り方を紹介してみる。

今回やったこと

  • UtakataのRailsアプリケーションに、ユーザーごとの短歌の一覧をJSONの配列で返すAPIを作成する
  • ローカル環境で動作するMCPサーバーを構築し、ユーザーごとの短歌の一覧を取得するToolを作成する
  • Claude DesktopにMCPサーバーを登録し、AIとのチャットでユーザーの投稿短歌についてやりとりできるようにする

GitHubリポジトリ

今回作ってみたMCPサーバーの実装を、動作確認できる形でGitHubに公開している。

Utakataにユーザーごとの短歌一覧APIを作成

今回のユースケースでは、MCPサーバーから、連携するアプリケーションのAPIをリクエストして情報を取得する仕組みだ。

そのためのAPIをまず用意する必要がある。以下のようなJSONの配列を返すAPIを作ってみた。

GET /api/users/:user_id/posts

[
  {
    "id": 25401,
    "published_at": "2021-05-15T17:39:00.000+09:00",
    "tanka_text": "片耳にマスクをかけて池の面をながれる風に呼応している",
    "likes_count": 32
  },
  {
    "id": 23563,
    "published_at": "2021-03-29T13:45:16.888+09:00",
    "tanka_text": "人びとの残りをもとめ散る花の上を歩いてゆく鳩の群れ",
    "likes_count": 17
  }
]

RailsでのAPIの実装方法の紹介は今回の本筋でないので省略するが、興味がある方はGitHubに公開されているUtakataの実装を参考にしてみて欲しい。

MCPサーバーの実装

公式のSDKが用意されているので、これを使って開発するのがお手軽だ。現状5種類の言語に対応されているようで、今回はtypescript-sdkを用いて、Node.js環境で動作するように実装する。

SDKのREADMEで、Resources, Tools, Promptsという3つの概念が紹介されていて、使い分けの理解が難しい印象があるが、試行錯誤してみて、Claude Desktopと連携して使う用途では、Toolとして作っておくのがお手軽に実行できて良さそうだった。

index.jsのファイルを作成し、以下のように、 fetch-user-tanka のToolを実装する。

Node.jsのfetchでUtakataのAPIから情報取得し、JSONの配列を整形して以下のようなテキストをアウトプットする仕組みだ。

# ユーザーID: 5 の短歌一覧(最新順)

- 片耳にマスクをかけて池の面をながれる風に呼応している(投稿日時: 2021-05-15T17:39:00.000+09:00、いいね数: 32)
- 人びとの残りをもとめ散る花の上を歩いてゆく鳩の群れ(投稿日時: 2021-03-29T13:45:16.888+09:00、いいね数: 17)

MCPサーバーの動作確認

MCP Inspectorという便利なツールが公式で用意されていて、ローカルで動作確認できる。

npx @modelcontextprotocol/inspector node index.js

Claude Desktopとの連携

MCPサーバーの動作確認ができたら、あとは生成AIツールと連携して使ってみるだけだ。MCPサーバーとの連携に対応している任意のツールと連携可能だが、今回はClaude Desktopと連携して使ってみる。この記事ではMacの環境で確認した結果を紹介する。

まずは、Claudeのデスクトップアプリケーションをインストールする。

公式ドキュメントに記載の方法を参考に連携設定する。Macの場合、Claudeのアプリケーション内の設定ではなく、Macのヘッダーメニューの「Claude > Settings > Developer」から該当メニューに辿り着く必要がある。「Get Started」ボタンを押すと設定ファイルが ~/Library/Application Support/Claude/claude_desktop_config.json のように作成されるので、エディタで編集してMCPサーバーとの連携設定を追加する。

{
  "mcpServers": {
    "utakataTankaReader": {
      "command": "/Users/fuyu77/.nodenv/shims/node",
      "args": [
        "/Users/fuyu77/mcp-utakata/index.js"
      ]
    }
  }
}

私の環境では、上のような設定で動作した。nodeコマンドとjsファイルのパスは、個別の環境の値に置き換える必要がある。

この設定を保存して、Claudeのアプリケーションを起動すると、Utakataの情報を参照したAIとのやりとりが可能になった。

TypeScriptでの実装

説明を簡単にするために、この記事ではJavaScriptでの実装で紹介したが、TypeScriptで実装してビルドする方法が上の公式ドキュメントに解説されている。

まとめ

MCPサーバーの実装はこのように簡単にできて、任意の情報システムと生成AIツールとの連携が可能になるので、業界で大いに注目されているのも頷ける印象だ。

Utakataの投稿短歌についても、この仕組みを活用して何か面白いことができるかも知れない。もしUtakataのMCPサーバーの活用について何かアイディアがある方がいれば、連絡先に記載のXアカウントのDMや、メールアドレスに連絡いただきたい。

ブログを書けなくなった

このブログに2024年に投稿された記事が1つもない。

ブログを書きたい気持ちがなくなった訳ではなく、人知れず下書きを書いてみたりはしているのだけれど、どうにもとりとめのない内容になってしまって、形にならない。諦めて放置するのも残念な感じがするので、開き直ってブログを書けないというブログを書いておくことにした。

まず、最近は仕事を頑張りすぎている、これが大きい。

仕事を頑張っているのだから仕事論を書けば良いのでは、となったりするのだけれど、無駄に長く書いた挙句に「この内容だったらその辺の仕事本を読んだ方が良いのでは」となったりしている。

ブログっていうのは、生活上の雑感をサラッと主観的に書くのが合ってるんだよな。

今年も仕事を頑張る部分は変わらなそうだけれど、極端に入れ込まずに、生活を充実させる部分もやっていきたい気がする。