روی یک مخزن ۴۶۰ فایلی، سه نشست هم‌زمان ساخته شد که هرکدام فایل خودش را داشتند و هیچ‌کدام کار دیگری را نبینند: هر پوشه ۱٫۹ مگابایت روی دیسک، اما تنها ۴ کیلوبایت متادیتای گیت در هر کدام. دو نشست همان فایل را ویرایش کردند و هیچ تداخلی رخ نداد؛ همان دو ویرایش در یک پوشه، یک ویرایش را بی‌صدا از بین برد. زمان خواندن داده: ۲۵ سپتامبر ۲۰۲۶.

مسئله‌ای که ذخیره‌سازی موقت حل نمی‌کند

وقتی دو کار روی یک مخزن دارید، پیش‌فرض ذهنی این است: یکی را ذخیره کن، دیگری را انجام بده، برگرد. در گیت این کار یعنی git stash، و در یک نشست تعاملی جلوی ایجنت همین اتفاق می‌افتد. مشکل این‌جاست که stash یک پشته است نه یک شاخه: هر نشست باید بداند نوبت نوبت کیست، و اگر یکی از نشست‌ها وسط کار متوقف شود، وضعیت آن روی دیسک می‌ماند و نشست بعدی آن را برنمی‌دارد.

این را اندازه گرفتم. در یک مخزن واقعی، دو ویرایش روی یک فایل، یکی پس از دیگری در یک پوشه:

$ git checkout -q -b seq
$ echo "written by A" > src/mod1.py && git commit -qam A
$ git stash -q
$ echo "written by B" > src/mod1.py && git commit -qam B
$ git stash pop -q
No stash entries found.
$ cat src/mod1.py
written by B

ویرایشِ نشست اول ناپدید شد و هیچ پیام خطایی هم نیامد. دستور stash pop گفت چیزی در پشته نیست، و فایل همان محتوای نشست دوم را داشت. این بدترین نوع شکست است: بی‌صدا، و دقیقاً در همان لحظه‌ای که فکر می‌کنید کار را مرتب کرده‌اید.

راه‌حل جایگزین، پاسخ خود گیت است: git worktree. هر نشست یک پوشه‌ی کامل فایل می‌گیرد، با شاخه‌ی خودش. تنها چیزی که مشترک می‌ماند انباره‌ی اشیای گیت است، نه فایل‌های کاری. همین حساب‌وکتاب در راهنمای کار درخت هرمز هم آمده است: هر نشست شاخه و پوشه‌ی خودش را می‌گیرد و تنها تاریخچه مشترک می‌ماند.

هر نشست چه چیزی می‌گیرد و چه چیزی مشترک می‌ماند

مهم‌ترین جزئیات پیاده‌سازی، یک خط است: فایل .git در هر کار درخت پیوندی یک فایل متنی چهار بایتی است که به مسیر انباره اشاره می‌کند. این را چاپ کردم:

$ cat wt-a/.git
gitdir: /root/.hermes/cache/scratch/wtrepo/.git/worktrees/wt-a
$ cat wt-b/.git
gitdir: /root/.hermes/cache/scratch/wtrepo/.git/worktrees/wt-b

یعنی هر کار درخت متادیتای خودش را دارد (سر وضعیت، فهرست فایل‌های نماهش‌داده‌شده) ولی اشیای فشرده، یعنی تاریخچه‌ی مشترک، یکی است. به همین دلیل ساختن کار درخت تقریباً آنی است: در دوازده بار اندازه‌گیری، کوتاه‌ترین ۴٫۶ میلی‌ثانیه، میانه ۴٫۸ و بلندترین ۶٫۵ میلی‌ثانیه.

اندازه‌ی واقعی روی دیسک را هم اندازه گرفتم. مخزن با ۴۶۰ فایل رهگیری‌شده، ۲٫۱ مگابایت بود و انباره‌ی اشیا پس از فشرده‌سازی ۶۰ کیلوبایت. سه کار درخت، هرکدام ۱٫۹ مگابایت و ۴۶۱ فایل:

مسیراندازهفایلمتادیتای گیت
مخزن اصلی۲٫۱ مگابایت۴۶۰۶۰ کیلوبایت (انباره‌ی اشیا)
کار درخت الفا۱٫۹ مگابایت۴۶۱۴ کیلوبایت (پیوند متنی)
کار درخت بتا۱٫۹ مگابایت۴۶۱۴ کیلوبایت
کار درخت گاما۱٫۹ مگابایت۴۶۱۴ کیلوبایت
جمع کار درخت‌ها۵٫۵ مگابایت۱٬۳۸۳۱۲ کیلوبایت

پس صورتحساب واقعی این است: هر نشست به اندازه‌ی سورس کاری فضا می‌گیرد، نه به اندازه‌ی تاریخچه. برای یک مخزن ۲۰ مگابایتی و چهار نشست، این یعنی حدود ۸۰ مگابایت فضای تازه در برابر چند مگابایت انباره‌ی مشترک. روی دیسک‌های امروزی ناچیز است، ولی در یک محیط ایزوله یا یک کانتینر کوچک همان چیزی است که باید از قبل بدانید.

اجرای هم‌زمان: آزمایش تعارض

حالا همان آزمایشی که با stash شکست خورد، با دو کار درخت. نشست الفا فایل را می‌نویسد و کامیت می‌کند؛ نشست بتا همان فایل را در همان لحظه می‌نویسد و کامیت می‌کند:

$ git worktree add -q ../wt-a -b feat/a
$ git worktree add -q ../wt-b -b feat/b

$ echo "written by A" > ../wt-a/src/mod1.py
$ git -C ../wt-a commit -qam "A edits mod1"

$ echo "written by B" > ../wt-b/src/mod1.py
$ git -C ../wt-b commit -qam "B edits mod1"

$ cat ../wt-a/src/mod1.py
written by A
$ cat ../wt-b/src/mod1.py
written by B
$ cat src/mod1.py
line 1

هر سه پاسخ درست‌اند. نشست الفا تغییر خودش را می‌بیند، بتا تغییر خودش را، و پوشه‌ی اصلی حتی خبر ندارد که چیزی اتفاق افتاده. هیچ فایل قفلی، هیچ وضعیت پاک‌سازی‌نشده و هیچ نشست معلقی به‌جا می‌ماند. تضاد عقب می‌رود به جایی که متعلق است: زمان ادغام، روی یک شاخه، با یک پیام صریح.

$ git -C ../wt-a merge feat/b
Auto-merging src/mod1.py
CONFLICT (content): Merge conflict in src/mod1.py
Automatic merge failed; fix conflicts and then commit the result.

این تفاوت میان دو حالت است. در روش پشته، تضاد پیش از ادغام و بی‌صدا اتفاق می‌افتد. در روش کار درخت، تضاد بعد از ادغام و با نام فایل و نوع تضاد اتفاق می‌افتد. اولی داده می‌نوشاند، دومی شما را متوقف می‌کند.

همین آزمایش یک نکته‌ی دوم را هم نشان می‌دهد که معمولاً پنهان می‌ماند: کار درخت‌ها فایل‌ها را از هم جدا می‌کنند، ولی تاریخچه را نه. هر دو شاخه از یک نقطه بیرون آمده‌اند و ادغام‌شان تضارض دارد. اگر انتظار داشتید کار درخت یعنی بی‌نیازی از ادغام، این پست آن ادعا را ندارد.

پرچم خود کلاد کد و آنچه در راهنما نیست

کلاد کد این الگو را خودش پیاده کرده و پرچمش در نسخه‌ی ۲٫۱٫۲۸۳ با همین متن تعریف شده است: -w, --worktree [name] با توضیح «برای این نشست یک کار درخت گیت تازه بساز، نام اختیاری». مسیر پیش‌فرض کار درخت‌ها داخل خود مخزن است، یعنی .claude/worktrees/، و این پوشه در فهرست نادیده‌گرفتن گیت قرار دارد تا وضعیت کار پاک‌سازی‌شده به مخزن برنگردد.

سه تنظیم همراه این پرچم وجود دارد که ارزش دانستن دارند. نخست worktree.baseRef با دو مقدار: fresh که پیش‌فرض است و از شاخه‌ی پیش‌فرض راه دور نقطه می‌زند تا درخت تمیز باشد، و head که از سر محلی فعلی شروع می‌کند تا کامیت‌های هنوز فرستاده‌نشده همراه باشند. اگر روی شاخه‌ای کار می‌کنید که کامیت محلی دارید و کد تازه‌ای هم گرفته‌اید، مقدار head معمولاً همان چیزی است که می‌خواهید.

دوم worktree.symlinkDirectories است، یعنی فهرستی از پوشه‌هایی مثل node_modules که به‌جای کپی، پیوند نمادین می‌شوند. نکته‌ی مهم این است که این تنظیم باید صریحا داده شود؛ به‌صورت پیش‌فرض هیچ پوشه‌ای پیوند داده نمی‌شود. بدون آن، هر نشست کل پوشه‌ی وابستگی‌ها را از نو می‌نویسد که برای یک پروژه‌ی جاوااسکریپت یعنی چند صد مگابایت به‌ازای هر نشست.

سوم worktree.sparsePaths که برای مخزن‌های بزرگ یک‌پارچه‌ی یکتا کار می‌کند. درون کد، تنظیم متناظر روشن می‌گوید تنها مسیرهای فهرست‌شده روی دیسک نوشته می‌شوند و این کار برای مونوریپوهای بزرگ به‌طور چشمگیر سریع‌تر است. اگر مخزن شما گیگابایت فایل دارد و هر نشست فقط با یک زیرشاخه کار دارد، این تنها اهرمی است که واقعاً هزینه را کم می‌کند. همین ایده در هرمز هم پرچم دارد: مرجع فرمان‌های خط فرمان گزینه‌ی --worktree را با نام کوتاه -w تعریف می‌کند و راهنمای خط فرمان همان حالت را با مثال اجرا نشان می‌دهد.

  • worktree.baseRef — نقطه‌ی شروع شاخه‌ی تازه: fresh از راه دور، head از سر محلی.
  • worktree.symlinkDirectories — پوشه‌هایی مثل node_modules که پیوند نمادین می‌شوند؛ خالی یعنی کپی کامل در هر نشست.
  • worktree.sparsePaths — تنها مسیرهای فهرست‌شده روی دیسک نوشته می‌شوند؛ اهرم اصلی در مونوریپو.
  • -w, --worktree [name] — خود پرچم، که مسیر کار درخت را داخل .claude/worktrees/ می‌سازد.

در سطح نشست هم دو ابزار وجود دارد. یکی در میانه‌ی کار یک کار درخت تازه می‌سازد و نشست را به آن منتقل می‌کند، و دیگری در میانه‌ی کار به پوشه‌ی قبلی برمی‌گردد. اگر نشستی از قبل در یک کار درخت باشد، ابزار ورود باید مسیر بگیرد وگرنه خطا می‌دهد؛ یعنی جابه‌جایی ناخواسته ممکن نیست.

برای کدکس مسیر متفاوت است و ارزش گفتن دارد. در حالی که این پست را می‌نویسم، بسته‌ی پایتونی openai-codex در رجیستری نسخه‌ی ۰٫۱۵۸٫۰ دارد و کلاس کار درختی در آن دیده نمی‌شود؛ در عوض کلاس ExternalMessage دارد که موضوع پست دیگری است. برای کدکس، الگوی کار درخت را خودتان با فرمان گیت می‌سازید و به هر نشست مسیر کاری تازه می‌دهید؛ یعنی همان اسکریپت بالا، بدون هیچ وابستگی تازه. همین خانواده‌ی نسخه‌ها در تاریخچه‌ی انتشار کدکس با تاریخ ثبت می‌شود، و همین الگوی کار درخت در پست کدکس و کار درخت جداگانه بررسی شده است.

آنچه بررسی نکردم

ادعای این پست محدود است: کار درخت گیت روی گیت ۲٫۴۳٫۰، سه نشست روی یک مخزن ۴۶۰ فایلی، و رفتار واقعی پرچم کلاد کد خوانده‌شده از تعریف همان نسخه. آنچه اندازه نگرفتم: اجرای واقعی دو فرایند ایجنت به‌طور هم‌زمان روی همین کار درخت‌ها. یعنی هزینه‌ی توکن و زمان واقعی چند نشست، اندازه‌گیری نشده است؛ اعداد بالا فقط درباره‌ی گیت‌اند، نه درباره‌ی مدل. پیاده‌سازی همین جداسازی در مخزن هرمز هست و آن بخش بیرون از ادعای این پست می‌ماند.

همچنین هیچ‌کدام از حالت‌های لبه را امتحان نکردم: زیرپوشه‌های تودرتو، زیرشاخه‌های کار درخت، و رفتار ابزار خودکارِ برچسب زدن هنگام خاموش شدن نشست. درون کد، پیام‌های خطایی وجود دارد که مربوط به همین حالت‌هاست، اما من آن‌ها را اجرا نکردم و ادعایی هم درباره‌شان ندارم.

جمع‌بندی عملی: اگر دو کار روی یک مخزن دارید که هرکدام فایل مشترک را لمس می‌کنند، کار درخت تنها چیزی است که از دست رفتن بی‌صدا را به یک تعارض اعلام‌شده تبدیل می‌کند. هزینه‌اش فضای فایل کاری به‌ازای هر نشست است، و در یک مخزن جاوااسکریپتی اگر پیوند نمادین را صریح تنظیم نکنید، هزینه‌ی اصلی همان‌جاست.

منابع

  1. راهنمای کار درخت هرمز — اجرای هم‌زمان چند ایجنت روی یک مخزن و حساب فضای هر نشست
  2. راهنمای خط فرمان هرمز — حالت کار درخت و فرمان‌های پاک‌سازی آن
  3. مرجع فرمان‌های خط فرمان هرمز — تعریف گزینه‌ی --worktree و گزینه‌های همراه آن
  4. مخزن هرمز — پیاده‌سازی کار درخت و ابزارهای نشست
  5. راهنمای وارد کردن نشست از ایجنت‌های دیگر — انتقال تنظیمات کلاد کد و کدکس به هرمز
  6. تاریخچه‌ی انتشار کدکس — شماره‌گذاری نسخه‌های خط فرمان کدکس
  7. راهنمای اجرای کدکس به‌عنوان سرور MCP — مرجع دو ابزار منتشرشده و پاسخ ساختاریافته
  8. افزونه‌ی کدکس برای کلاد کد — واگذاری کار از یک نشست به نشست دیگر