[{"data":1,"prerenderedAt":1047},["ShallowReactive",2],{"article-worktrees-with-worktrunk":3,"articles-100":925,"articles-10":1026},{"id":4,"title":5,"alt":6,"body":7,"date":907,"description":908,"extension":909,"image":910,"meta":911,"navigation":582,"ogImage":907,"path":912,"postDate":913,"provider":907,"published":914,"robots":907,"schemaOrg":907,"seo":915,"sitemap":916,"stem":917,"tags":918,"url":907,"__hash__":924},"blog\u002Fblog\u002Fworktrees-with-worktrunk.md","Stop stashing half-finished work: Git worktrees and Worktrunk","Three parallel Git worktree directories side by side, each with its own branch checked out",{"type":8,"value":9,"toc":890},"minimark",[10,14,17,20,23,26,36,41,44,49,52,55,58,69,82,89,92,96,111,114,143,149,155,161,164,217,220,255,263,266,291,305,308,312,323,326,360,363,366,370,378,382,385,414,426,429,447,464,503,507,523,548,552,555,566,612,620,624,627,630,633,662,686,689,692,696,700,703,763,766,769,773,776,779,785,834,844,847,883,886],[11,12,13],"p",{},"I’ve had this happen more times than I can count. I finish implementing changes for a ticket, put it into code review, and start the next one. Twenty minutes later, a reviewer asks for a 2-line change on the first ticket.",[11,15,16],{},"The change could take a few minutes for me to make but getting back to the right code can take longer.",[11,18,19],{},"My current branch already has modified files. Maybe the application is running with some local state I’ll need again, or I have configured the env variables differently for what I am currently working on. I can stash everything, switch branches, make the fix, switch back, restore the stash, and hope I didn’t forget which migration or environment variable belonged to which task.",[11,21,22],{},"Or I can make a work-in-progress commit that I never wanted in the branch. Or create another temporary branch whose name I’ll forget soon. Or leave the review sitting there until I finish my current work.",[11,24,25],{},"All of those work to some extent, and I have done them in the past. But the problem is that they turn a tiny review comment into a small ceremony.",[11,27,28,35],{},[29,30,34],"a",{"href":31,"rel":32},"https:\u002F\u002Fgit-scm.com\u002Fdocs\u002Fgit-worktree",[33],"nofollow","Git worktrees"," give me a better option: keep multiple branches checked out at the same time, in separate directories.",[37,38,40],"h2",{"id":39},"what-git-worktrees-give-you","What Git worktrees give you",[11,42,43],{},"Git worktrees give you a separate working directory per branch, all in the same repository. You can keep more than one branch checked out at the same time, each with its own files, editor state, and running dev server.",[45,46,48],"h3",{"id":47},"a-branch-and-a-worktree-arent-the-same-thing","A branch and a worktree aren’t the same thing",[11,50,51],{},"A branch is a movable reference to a commit. A working tree is the checked-out set of files I edit. A worktree, in Git’s terminology, is that working tree plus the Git metadata that makes it an independent checkout.",[11,53,54],{},"My normal clone is already one worktree (Git calls it the main worktree). I can attach more linked worktrees to the same repository, with a different branch checked out in each one.",[11,56,57],{},"For example:",[59,60,66],"pre",{"className":61,"code":63,"language":64,"meta":65},[62],"language-text","~\u002Fcode\u002Fshop\u002F                    # main worktree, branch: feature\u002Fnew-checkout\n~\u002Fcode\u002Fshop.review-fix\u002F         # linked worktree, branch: fix\u002Fpayment-review\n~\u002Fcode\u002Fshop.order-history\u002F      # linked worktree, branch: feature\u002Forder-history\n","text","",[67,68,63],"code",{"__ignoreMap":65},[11,70,71,72,75,76,81],{},"These directories belong to the same repository. They share Git’s object database and refs, while keeping per-worktree state such as ",[67,73,74],{},"HEAD"," and the index separate (",[29,77,80],{"href":78,"rel":79},"https:\u002F\u002Fgit-scm.com\u002Fdocs\u002Fgit-worktree#_description",[33],"Git worktree documentation",").",[11,83,84,85,88],{},"In practical terms, each directory has its own checked-out files, staged changes, and untracked files. I can keep an editor and terminal open in each one, leave the new checkout work exactly as it is, address the review in ",[67,86,87],{},"shop.review-fix",", and return without stashing.",[11,90,91],{},"The cognitive cost of context switching is still there, but the Git housekeeping is gone. My editor of choice, Zed, has built-in worktree support with a worktree picker in the command palette, so opening a different worktree is one command away.",[45,93,95],{"id":94},"creating-the-review-fix-worktree-with-git","Creating the review-fix worktree with Git",[11,97,98,99,102,103,106,107,110],{},"Suppose I’m in ",[67,100,101],{},"~\u002Fcode\u002Fshop",", working on ",[67,104,105],{},"feature\u002Fnew-checkout",", and the earlier branch is called ",[67,108,109],{},"fix\u002Fpayment-review",".",[11,112,113],{},"First I create a linked worktree for the existing branch:",[59,115,119],{"className":116,"code":117,"language":118,"meta":65,"style":65},"language-bash shiki shiki-themes github-light github-dark","git worktree add ..\u002Fshop.review-fix fix\u002Fpayment-review\n","bash",[67,120,121],{"__ignoreMap":65},[122,123,126,130,134,137,140],"span",{"class":124,"line":125},"line",1,[122,127,129],{"class":128},"sScJk","git",[122,131,133],{"class":132},"sZZnC"," worktree",[122,135,136],{"class":132}," add",[122,138,139],{"class":132}," ..\u002Fshop.review-fix",[122,141,142],{"class":132}," fix\u002Fpayment-review\n",[11,144,145,146,148],{},"My current directory stays on ",[67,147,105],{},", including all its unfinished changes.",[11,150,151,154],{},[67,152,153],{},"git worktree list"," shows every attached worktree:",[59,156,159],{"className":157,"code":158,"language":64,"meta":65},[62],"~\u002Fcode\u002Fshop             7db81af [feature\u002Fnew-checkout]\n~\u002Fcode\u002Fshop.review-fix  b132aa0 [fix\u002Fpayment-review]\n",[67,160,158],{"__ignoreMap":65},[11,162,163],{},"Now I can handle the review in the review-fix worktree:",[59,165,167],{"className":116,"code":166,"language":118,"meta":65,"style":65},"cd ..\u002Fshop.review-fix\n# make the requested change\ngit add . && git commit -m \"Fix payment retry condition\" && git push\n",[67,168,169,178,185],{"__ignoreMap":65},[122,170,171,175],{"class":124,"line":125},[122,172,174],{"class":173},"sj4cs","cd",[122,176,177],{"class":132}," ..\u002Fshop.review-fix\n",[122,179,181],{"class":124,"line":180},2,[122,182,184],{"class":183},"sJ8bj","# make the requested change\n",[122,186,188,190,192,195,199,201,204,207,210,212,214],{"class":124,"line":187},3,[122,189,129],{"class":128},[122,191,136],{"class":132},[122,193,194],{"class":132}," .",[122,196,198],{"class":197},"sVt8B"," && ",[122,200,129],{"class":128},[122,202,203],{"class":132}," commit",[122,205,206],{"class":173}," -m",[122,208,209],{"class":132}," \"Fix payment retry condition\"",[122,211,198],{"class":197},[122,213,129],{"class":128},[122,215,216],{"class":132}," push\n",[11,218,219],{},"When DevQA is finished and I don’t need the checkout anymore:",[59,221,223],{"className":116,"code":222,"language":118,"meta":65,"style":65},"cd ..\u002Fshop\ngit worktree remove ..\u002Fshop.review-fix\ngit branch -d fix\u002Fpayment-review\n",[67,224,225,232,243],{"__ignoreMap":65},[122,226,227,229],{"class":124,"line":125},[122,228,174],{"class":173},[122,230,231],{"class":132}," ..\u002Fshop\n",[122,233,234,236,238,241],{"class":124,"line":180},[122,235,129],{"class":128},[122,237,133],{"class":132},[122,239,240],{"class":132}," remove",[122,242,177],{"class":132},[122,244,245,247,250,253],{"class":124,"line":187},[122,246,129],{"class":128},[122,248,249],{"class":132}," branch",[122,251,252],{"class":173}," -d",[122,254,142],{"class":132},[11,256,257,258,81],{},"Branch deletion is a separate step in native Git. Git normally refuses to remove a dirty worktree, and it also refuses to check out the same branch in another worktree unless I override its safeguards (",[29,259,262],{"href":260,"rel":261},"https:\u002F\u002Fgit-scm.com\u002Fdocs\u002Fgit-worktree#_commands",[33],"Git worktree commands and options",[11,264,265],{},"For a brand-new task, I can create the branch and worktree together:",[59,267,269],{"className":116,"code":268,"language":118,"meta":65,"style":65},"git worktree add -b feature\u002Forder-history ..\u002Fshop.order-history main\n",[67,270,271],{"__ignoreMap":65},[122,272,273,275,277,279,282,285,288],{"class":124,"line":125},[122,274,129],{"class":128},[122,276,133],{"class":132},[122,278,136],{"class":132},[122,280,281],{"class":173}," -b",[122,283,284],{"class":132}," feature\u002Forder-history",[122,286,287],{"class":132}," ..\u002Fshop.order-history",[122,289,290],{"class":132}," main\n",[11,292,293,294,297,298,301,302,110],{},"That creates ",[67,295,296],{},"feature\u002Forder-history"," from ",[67,299,300],{},"main"," and checks it out in ",[67,303,304],{},"..\u002Fshop.order-history",[11,306,307],{},"This is already enough to improve the review interruption. The part that started to annoy me came next.",[45,309,311],{"id":310},"every-new-directory-needs-its-project-setup","Every new directory needs its project setup",[11,313,314,315,318,319,322],{},"A linked worktree contains the tracked files from its branch. My ",[67,316,317],{},".env",", ",[67,320,321],{},"node_modules",", build output, local database, and other ignored files belong to a particular working directory. A new worktree won’t magically have them.",[11,324,325],{},"So the first few minutes often become:",[59,327,329],{"className":116,"code":328,"language":118,"meta":65,"style":65},"cp ..\u002Fshop\u002F.env .env\nnpm install\nnpm run generate\n",[67,330,331,342,350],{"__ignoreMap":65},[122,332,333,336,339],{"class":124,"line":125},[122,334,335],{"class":128},"cp",[122,337,338],{"class":132}," ..\u002Fshop\u002F.env",[122,340,341],{"class":132}," .env\n",[122,343,344,347],{"class":124,"line":180},[122,345,346],{"class":128},"npm",[122,348,349],{"class":132}," install\n",[122,351,352,354,357],{"class":124,"line":187},[122,353,346],{"class":128},[122,355,356],{"class":132}," run",[122,358,359],{"class":132}," generate\n",[11,361,362],{},"Then I remember that the dev server in another worktree already owns port 3000.",[11,364,365],{},"I can solve this with a shell function or a project script, but once worktrees become a daily workflow, the function starts collecting branch naming rules, directory naming rules, setup steps, cleanup steps, and special cases.",[37,367,369],{"id":368},"where-worktrunk-comes-in","Where Worktrunk comes in",[11,371,372,377],{},[29,373,376],{"href":374,"rel":375},"https:\u002F\u002Fgithub.com\u002Fmax-sixty\u002Fworktrunk",[33],"Worktrunk"," wraps Git worktrees with a branch-oriented CLI and lifecycle hooks. I still get ordinary Git worktrees underneath. Worktrunk takes care of the repetitive setup around them.",[45,379,381],{"id":380},"making-worktrunk-do-the-boring-parts","Making Worktrunk do the boring parts",[11,383,384],{},"On macOS or Linux with Homebrew, the current installation command is:",[59,386,388],{"className":116,"code":387,"language":118,"meta":65,"style":65},"brew install worktrunk\nwt config shell install\n",[67,389,390,401],{"__ignoreMap":65},[122,391,392,395,398],{"class":124,"line":125},[122,393,394],{"class":128},"brew",[122,396,397],{"class":132}," install",[122,399,400],{"class":132}," worktrunk\n",[122,402,403,406,409,412],{"class":124,"line":180},[122,404,405],{"class":128},"wt",[122,407,408],{"class":132}," config",[122,410,411],{"class":132}," shell",[122,413,349],{"class":132},[11,415,416,417,420,421,81],{},"The shell integration matters because ",[67,418,419],{},"wt switch"," needs to change the current shell’s directory (",[29,422,425],{"href":423,"rel":424},"https:\u002F\u002Fworktrunk.dev\u002F#install",[33],"Worktrunk installation guide",[11,427,428],{},"Now I can create and enter a worktree with:",[59,430,432],{"className":116,"code":431,"language":118,"meta":65,"style":65},"wt switch --create feature\u002Forder-history\n",[67,433,434],{"__ignoreMap":65},[122,435,436,438,441,444],{"class":124,"line":125},[122,437,405],{"class":128},[122,439,440],{"class":132}," switch",[122,442,443],{"class":173}," --create",[122,445,446],{"class":132}," feature\u002Forder-history\n",[11,448,449,450,452,453,456,457,81],{},"Worktrunk addresses worktrees by branch name and calculates the directory from a configurable template. If the branch already has a worktree, ",[67,451,419],{}," moves me into it; if not, ",[67,454,455],{},"wt switch --create"," makes one. With no argument, it opens an interactive picker with branch status and diff previews (",[29,458,461,463],{"href":459,"rel":460},"https:\u002F\u002Fworktrunk.dev\u002Fswitch\u002F",[33],[67,462,419],{}," documentation",[11,465,466,467,318,469,472,473,478,479,482,483,485,486,489,490,496,497,502],{},"The commands I use most are ",[67,468,419],{},[67,470,471],{},"wt list"," (",[29,474,477],{"href":475,"rel":476},"https:\u002F\u002Fworktrunk.dev\u002Flist\u002F",[33],"reference","), and ",[67,480,481],{},"wt remove",". ",[67,484,481],{}," defaults to the current worktree and only deletes the branch when Worktrunk determines it's already merged; ",[67,487,488],{},"--no-delete-branch"," keeps it (",[29,491,494,463],{"href":492,"rel":493},"https:\u002F\u002Fworktrunk.dev\u002Fremove\u002F",[33],[67,495,481],{},"). See the ",[29,498,501],{"href":499,"rel":500},"https:\u002F\u002Fworktrunk.dev\u002F",[33],"Worktrunk reference"," for the full list.",[45,504,506],{"id":505},"project-hooks-turn-a-checkout-into-a-usable-workspace","Project hooks turn a checkout into a usable workspace",[11,508,509,510,513,514,517,518,110],{},"Every worktree starts as a clean slate. The same setup repeats every time: install dependencies, run project generators, register the worktree with the rest of the toolchain on switch, and unregister it on removal. My hooks do all four from a shared config file, so creating a worktree returns me to \"ready to work.\" The full reference, including the ",[67,511,512],{},"pre-merge"," and ",[67,515,516],{},"post-merge"," lifecycles and the approval flow for shared configs, is in the ",[29,519,522],{"href":520,"rel":521},"https:\u002F\u002Fworktrunk.dev\u002Fhook\u002F",[33],"Worktrunk hook docs",[11,524,525,526,529,530,533,534,537,538,540,541,543,544,547],{},"Worktrunk reads those hooks from ",[67,527,528],{},".config\u002Fwt.toml",". They fire at lifecycle points like ",[67,531,532],{},"pre-start"," (before the worktree is usable) and ",[67,535,536],{},"post-start"," (after, in the background). I keep blocking setup in ",[67,539,532],{}," and push anything slow to ",[67,542,536],{}," so creating a worktree returns fast. There's also ",[67,545,546],{},"post-switch",", which fires on every checkout rather than just creation. That's the right home for work that needs to track the current branch, like the Herd link in the closing config.",[45,549,551],{"id":550},"giving-every-worktree-its-own-dev-server","Giving every worktree its own dev server",[11,553,554],{},"Separate source directories can still fight over shared resources. Two applications can’t both listen on port 3000, and two test runs pointed at the same mutable database can produce some creative failures. But the upside is that a long test suite in one worktree doesn’t block me from checking out main in another, since the other worktree isn’t competing for the same resources.",[11,556,557,558,561,562,565],{},"Worktrunk’s ",[67,559,560],{},"hash_port"," template filter derives a stable port between 10000 and 19999 from the branch name. Its ",[67,563,564],{},"tether"," step runs a process in its own process group and tears that group down when the worktree is removed.",[59,567,571],{"className":568,"code":569,"language":570,"meta":65,"style":65},"language-toml shiki shiki-themes github-light github-dark","# .config\u002Fwt.toml\n\n[post-start]\nserver = \"wt step tether -- npm run dev -- --port {{ branch | hash_port }}\"\n\n[list]\nurl = \"http:\u002F\u002Flocalhost:{{ branch | hash_port }}\"\n","toml",[67,572,573,578,584,589,595,600,606],{"__ignoreMap":65},[122,574,575],{"class":124,"line":125},[122,576,577],{},"# .config\u002Fwt.toml\n",[122,579,580],{"class":124,"line":180},[122,581,583],{"emptyLinePlaceholder":582},true,"\n",[122,585,586],{"class":124,"line":187},[122,587,588],{},"[post-start]\n",[122,590,592],{"class":124,"line":591},4,[122,593,594],{},"server = \"wt step tether -- npm run dev -- --port {{ branch | hash_port }}\"\n",[122,596,598],{"class":124,"line":597},5,[122,599,583],{"emptyLinePlaceholder":582},[122,601,603],{"class":124,"line":602},6,[122,604,605],{},"[list]\n",[122,607,609],{"class":124,"line":608},7,[122,610,611],{},"url = \"http:\u002F\u002Flocalhost:{{ branch | hash_port }}\"\n",[11,613,614,615,81],{},"The server starts in the background, so creating the worktree doesn’t wait for a long-running process (",[29,616,619],{"href":617,"rel":618},"https:\u002F\u002Fworktrunk.dev\u002Ftips-patterns\u002F#dev-server-per-worktree",[33],"Worktrunk’s per-worktree dev-server recipe",[45,621,623],{"id":622},"worktrees-fit-coding-agents-unusually-well","Worktrees fit coding agents unusually well",[11,625,626],{},"Worktrunk becomes even more useful when I have multiple LLM coding agents working against the same repository.",[11,628,629],{},"Giving two agents the same working directory is an invitation to edit the same files, stage each other’s changes, or run cleanup commands against work neither agent created. A worktree gives each agent its own branch, index, and checked-out files.",[11,631,632],{},"Worktrunk can create the worktree, run the project hooks, move into it, and start an agent in one command:",[59,634,636],{"className":116,"code":635,"language":118,"meta":65,"style":65},"wt switch --create --execute=claude feature\u002Forder-history -- \\\n  \"Add order history to the account page\"\n",[67,637,638,657],{"__ignoreMap":65},[122,639,640,642,644,646,649,651,654],{"class":124,"line":125},[122,641,405],{"class":128},[122,643,440],{"class":132},[122,645,443],{"class":173},[122,647,648],{"class":173}," --execute=claude",[122,650,284],{"class":132},[122,652,653],{"class":173}," --",[122,655,656],{"class":173}," \\\n",[122,658,659],{"class":124,"line":180},[122,660,661],{"class":132},"  \"Add order history to the account page\"\n",[11,663,664,665,668,669,671,672,675,676,678,679,81],{},"I can run a second agent in another terminal with the same pattern. Arguments after ",[67,666,667],{},"--"," are passed to the launched command; the blocking ",[67,670,532],{}," hooks finish before ",[67,673,674],{},"--execute"," runs, while ",[67,677,536],{}," hooks continue in the background, so each agent begins in a prepared directory (",[29,680,557,683,685],{"href":681,"rel":682},"https:\u002F\u002Fworktrunk.dev\u002Fswitch\u002F#creating-worktrees",[33],[67,684,674],{}," workflow",[11,687,688],{},"Their changes can still conflict when I merge the branches, and they can still collide through a shared database, port, container name, or generated cache outside the worktree. The filesystem boundary solves a specific class of problems, and I still need to divide the tasks sensibly and review what comes back.",[11,690,691],{},"I also don’t need five agents running because the tool makes it possible. Parallel work is useful when the tasks are independent enough that the merge and review cost stays lower than the time saved.",[37,693,695],{"id":694},"trade-offs-and-takeaway","Trade-offs and takeaway",[45,697,699],{"id":698},"the-trade-offs-and-when-id-skip","The trade-offs, and when I’d skip",[11,701,702],{},"Worktrees have a few edges that are easy to miss:",[704,705,706,710,713,716,719,741,755],"ul",{},[707,708,709],"li",{},"Every worktree duplicates the checked-out project files. Dependencies and build output can consume much more disk space than the source.",[707,711,712],{},"Git normally allows a branch to be checked out in only one worktree at a time. I treat the directory and branch as a pair.",[707,714,715],{},"State outside the worktree needs explicit handling. For example: ignored files and config need to be copied or generated, and shared resources (dev servers, databases, Docker, caches) need unique names or ports.",[707,717,718],{},"Worktrees are easy to make and easy to forget. Usually, I get remove the worktrees after the work has been marked as done by QA testing.",[707,720,721,722,725,726,729,730,732,733,736,737,81],{},"Removing a directory with ",[67,723,724],{},"rm -rf"," leaves worktree administration behind. I use ",[67,727,728],{},"git worktree remove"," or ",[67,731,481],{},"; ",[67,734,735],{},"git worktree prune"," cleans up stale metadata after a manual deletion (",[29,738,740],{"href":78,"rel":739},[33],"Git worktree cleanup",[707,742,743,744,747,748,751,752,754],{},"A third-party Git hook that runs on ",[67,745,746],{},"post-checkout"," can short-circuit ",[67,749,750],{},"git worktree add"," if it depends on a binary that doesn't exist in a fresh worktree. Patch the hook to exit cleanly when the dependency is missing, or ",[67,753,455],{}," fails before it can start.",[707,756,757,758,81],{},"Git still documents submodule support across multiple worktrees as incomplete, so I’d test the workflow carefully in a superproject with submodules (",[29,759,762],{"href":760,"rel":761},"https:\u002F\u002Fgit-scm.com\u002Fdocs\u002Fgit-worktree#_bugs",[33],"Git worktree bugs",[11,764,765],{},"I’d skip worktrees entirely when those edges outweigh the benefits, like if I’m on one branch at a time, the repo is huge and disk is tight, or the IDE doesn’t support worktree.",[11,767,768],{},"There’s also the human limit. Keeping four tasks open in four directories makes switching between them mechanically cheap, but it doesn’t make holding four trains of thought equally cheap. What works for me is to keep the main task untouched, handle the review or production fix in another worktree, then remove it when the branch is done. When an agent can take an independent task, I give it a worktree too.",[45,770,772],{"id":771},"my-takeaway","My takeaway",[11,774,775],{},"Git worktrees turn branches into persistent working directories. That alone removes the stash-switch-unstash loop from review fixes and interruptions.",[11,777,778],{},"Worktrunk earns its place when creating a directory isn’t enough. It gives me short branch-based commands, project hooks, repeatable paths, cleanup checks, and a clean way to launch tools inside a prepared worktree.",[11,780,781,782,784],{},"For a sense of what the resulting ",[67,783,528],{}," actually looks like, here’s the version I shipped in a real repository:",[59,786,788],{"className":568,"code":787,"language":570,"meta":65,"style":65},"[post-start]\nnode = \"sh '{{ primary_worktree_path }}\u002Fscripts\u002Fworktrunk\u002Fsetup-node.sh'\"\nphp = \"sh '{{ primary_worktree_path }}\u002Fscripts\u002Fworktrunk\u002Fsetup-php.sh' '{{ branch }}' '{{ worktree_name }}' '{{ primary_worktree_path }}'\"\n\n[post-switch]\nlink = \"sh '{{ primary_worktree_path }}\u002Fscripts\u002Fworktrunk\u002Fherd-link.sh' '{{ branch }}' '{{ worktree_name }}'\"\n\n[pre-remove]\nunlink = \"sh '{{ primary_worktree_path }}\u002Fscripts\u002Fworktrunk\u002Fherd-unlink.sh' '{{ branch }}' '{{ worktree_name }}'\"\n",[67,789,790,794,799,804,808,813,818,822,828],{"__ignoreMap":65},[122,791,792],{"class":124,"line":125},[122,793,588],{},[122,795,796],{"class":124,"line":180},[122,797,798],{},"node = \"sh '{{ primary_worktree_path }}\u002Fscripts\u002Fworktrunk\u002Fsetup-node.sh'\"\n",[122,800,801],{"class":124,"line":187},[122,802,803],{},"php = \"sh '{{ primary_worktree_path }}\u002Fscripts\u002Fworktrunk\u002Fsetup-php.sh' '{{ branch }}' '{{ worktree_name }}' '{{ primary_worktree_path }}'\"\n",[122,805,806],{"class":124,"line":591},[122,807,583],{"emptyLinePlaceholder":582},[122,809,810],{"class":124,"line":597},[122,811,812],{},"[post-switch]\n",[122,814,815],{"class":124,"line":602},[122,816,817],{},"link = \"sh '{{ primary_worktree_path }}\u002Fscripts\u002Fworktrunk\u002Fherd-link.sh' '{{ branch }}' '{{ worktree_name }}'\"\n",[122,819,820],{"class":124,"line":608},[122,821,583],{"emptyLinePlaceholder":582},[122,823,825],{"class":124,"line":824},8,[122,826,827],{},"[pre-remove]\n",[122,829,831],{"class":124,"line":830},9,[122,832,833],{},"unlink = \"sh '{{ primary_worktree_path }}\u002Fscripts\u002Fworktrunk\u002Fherd-unlink.sh' '{{ branch }}' '{{ worktree_name }}'\"\n",[11,835,836,837,840,841,843],{},"Every script takes the worktree's name, does the project-specific work, and the cleanup script mirrors the setup script. ",[67,838,839],{},"{{ primary_worktree_path }}"," is what lets each script reach the canonical ",[67,842,317],{}," without me ever hardcoding a path.",[11,845,846],{},"I’d start with the native commands until the model makes sense:",[59,848,850],{"className":116,"code":849,"language":118,"meta":65,"style":65},"git worktree add ..\u002Fshop.review-fix fix\u002Fpayment-review\ngit worktree list\ngit worktree remove ..\u002Fshop.review-fix\n",[67,851,852,864,873],{"__ignoreMap":65},[122,853,854,856,858,860,862],{"class":124,"line":125},[122,855,129],{"class":128},[122,857,133],{"class":132},[122,859,136],{"class":132},[122,861,139],{"class":132},[122,863,142],{"class":132},[122,865,866,868,870],{"class":124,"line":180},[122,867,129],{"class":128},[122,869,133],{"class":132},[122,871,872],{"class":132}," list\n",[122,874,875,877,879,881],{"class":124,"line":187},[122,876,129],{"class":128},[122,878,133],{"class":132},[122,880,240],{"class":132},[122,882,177],{"class":132},[11,884,885],{},"Then I’d add Worktrunk when the repeated environment setup starts getting irritating.",[887,888,889],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":65,"searchDepth":180,"depth":180,"links":891},[892,897,903],{"id":39,"depth":180,"text":40,"children":893},[894,895,896],{"id":47,"depth":187,"text":48},{"id":94,"depth":187,"text":95},{"id":310,"depth":187,"text":311},{"id":368,"depth":180,"text":369,"children":898},[899,900,901,902],{"id":380,"depth":187,"text":381},{"id":505,"depth":187,"text":506},{"id":550,"depth":187,"text":551},{"id":622,"depth":187,"text":623},{"id":694,"depth":180,"text":695,"children":904},[905,906],{"id":698,"depth":187,"text":699},{"id":771,"depth":187,"text":772},null,"Git worktrees let you keep multiple branches checked out at once, so a review fix doesn't mean stashing your current work. Worktrunk automates the per-worktree setup, and the same isolation also gives parallel AI coding agents their own working directories.","md","\u002Fimages\u002Fgit-worktrees.png",{},"\u002Fblog\u002Fworktrees-with-worktrunk","2026-08-16",false,{"title":5,"description":908},{"loc":912},"blog\u002Fworktrees-with-worktrunk",[129,919,920,921,922,923],"worktrees","worktrunk","developer productivity","command line","workflow","_Y5cwyFsl-T0MDouBISFoFty5zDY_BAiDGyzoNNNO60",[926,928,938,944,952,961,970,979,985,991,1000,1007,1013,1020],{"title":5,"description":908,"tags":927,"postDate":913,"path":912,"url":907},[129,919,920,921,922,923],{"title":929,"description":930,"tags":931,"postDate":936,"path":937,"url":907},"Self-Hosting my Git forge with Forgejo","Why I moved away from GitHub and chose a lightweight, community-driven alternative",[932,933,934,935],"Homelab","Open source","Self-hosting","Workflow","2026-04-29","\u002Fblog\u002Fself-hosting-my-git-forge",{"title":939,"description":940,"tags":941,"postDate":942,"path":943,"url":907},"Curating my own reading and discovery feed with RSS","Taking concrete steps away from algorithms using FreshRSS, self-hosted tools, and a pipeline built for me.",[932,933,934,935],"2026-02-08","\u002Fblog\u002Freading-and-discovery-feed-curation",{"title":945,"description":946,"tags":947,"postDate":950,"path":951,"url":907},"Fixing my broken Docker Immich+Traefik setup","Photo indexing and other jobs failed in my Immich+Traefik setup. Turns out I'd misconfigured my Docker networking with Traefik. Here's how I figured it out and what I changed.",[948,949],"homelab","networking","2025-10-31","\u002Fblog\u002Ffixing-docker-immich-traefik-setup",{"title":953,"description":954,"tags":955,"postDate":959,"path":960,"url":907},"Quickly Outline Large JSON Responses with a Custom jq Function","Use jq to automatically map and visualize the structure of large JSON responses, perfect for exploring undocumented endpoints.",[956,957,922,958,921],"jq","json","automation","2025-10-19","\u002Fblog\u002Fjq-json-outline",{"title":962,"description":963,"tags":964,"postDate":968,"path":969,"url":907},"Why I added RSS feeds to my blog (and how I did it in Nuxt 3)","How I built RSS and Atom feed support for my Nuxt 3 website to let readers follow my content without social media algorithms.",[965,966,967],"nuxt","web development","tutorial","2025-08-31","\u002Fblog\u002Fsetting-up-rss-atom-feeds-nuxt3",{"title":971,"description":972,"tags":973,"postDate":977,"path":978,"url":907},"Revisiting my Stream Deck for music control","How I turned my neglected Stream Deck into a useful tool for controlling YouTube Music without breaking my workflow.",[974,975,958,976],"productivity","Developer Productivity","home-office","2025-06-07","\u002Fblog\u002Fstream-deck-youtube-music",{"title":980,"description":981,"tags":982,"postDate":983,"path":984,"url":907},"fzf + fabric: Smart selection for LLM patterns and models","Enhance your experience with fabric, the AI prompt tool, by using interactive selection for patterns and models.",[921],"2025-05-24","\u002Fblog\u002Fai-pattern-selection-in-fabric",{"title":986,"description":987,"tags":988,"postDate":989,"path":990,"url":907},"Effortless configuration syncing with GNU Stow and Git: A software engineer's workflow","Discover how to manage and sync configuration files across Linux and macOS using GNU Stow and Git. Simplify your workflow with centralized, version-controlled setups.",[921],"2025-01-12","\u002Fblog\u002Feffortless-configuration-syncing-with-gnu-stow",{"title":992,"description":993,"tags":994,"postDate":998,"path":999,"url":907},"My 2023 travels: A visual journey","Looking back at the best pictures from my travel adventures in 2023",[995,996,997],"personal","travel","photography","2024-01-01","\u002Fblog\u002F2023-travels-visual-journey",{"title":1001,"description":1002,"tags":1003,"postDate":1005,"path":1006,"url":907},"Re-living memories through proper media organization","Organizing media such as photos and videos can bring back cherished memories and help relive them. Here's my approach to organizing my digital media collection.",[995,1004],"technology","2023-02-06","\u002Fblog\u002Freliving-memories-through-proper-media-organization",{"title":1008,"description":1009,"tags":1010,"postDate":1011,"path":1012,"url":907},"Looking back: My 2022 in review","From new adventures and memories to lessons learned and personal growth, this year has been a year of discovery. Here's my 2022 in review.",[995,996],"2022-12-31","\u002Fblog\u002F2022-in-review",{"title":1014,"description":1015,"tags":1016,"postDate":1018,"path":1019,"url":907},"Git and the command-line: Take your workflow to the next level","Some tips on how to improve your Git productivity using the command-line",[1017,921],"programming","2022-12-18","\u002Fblog\u002Fgit-and-command-line-next-level-workflow",{"title":1021,"description":1022,"tags":1023,"postDate":1024,"path":1025,"url":907},"Hiking Tsho-Rolpa lake trail in Nepal","Details of my hiking journey to the Tsho-Rolpa glacial lake in Nepal 🇳🇵, situated at 4,580m from sea level",[996],"2022-01-22","\u002Fblog\u002Fhiking-tsho-rolpa-lake-trail-in-nepal",[1027,1029,1031,1033,1035,1037,1039,1041,1043,1045],{"title":5,"description":908,"tags":1028,"postDate":913,"path":912,"url":907},[129,919,920,921,922,923],{"title":929,"description":930,"tags":1030,"postDate":936,"path":937,"url":907},[932,933,934,935],{"title":939,"description":940,"tags":1032,"postDate":942,"path":943,"url":907},[932,933,934,935],{"title":945,"description":946,"tags":1034,"postDate":950,"path":951,"url":907},[948,949],{"title":953,"description":954,"tags":1036,"postDate":959,"path":960,"url":907},[956,957,922,958,921],{"title":962,"description":963,"tags":1038,"postDate":968,"path":969,"url":907},[965,966,967],{"title":971,"description":972,"tags":1040,"postDate":977,"path":978,"url":907},[974,975,958,976],{"title":980,"description":981,"tags":1042,"postDate":983,"path":984,"url":907},[921],{"title":986,"description":987,"tags":1044,"postDate":989,"path":990,"url":907},[921],{"title":992,"description":993,"tags":1046,"postDate":998,"path":999,"url":907},[995,996,997],1786907529736]