{"slug": "how-i-built-a-windows-version-of-ghostty-in-3-days-with-ai", "title": "How I Built a Windows Version of Ghostty in 3 Days with AI", "summary": "A developer forked the Ghostty terminal emulator and produced a working Windows version in three days, writing 48,000 lines of Rust across 45 files without authoring any code by hand. The port was driven by an AI CLI manager system in which one Claude session coordinated three to four Claude worker sessions through a shared task board and group chat, with the developer setting direction and enforcing rules such as barring workers from git operations. Rust was chosen over Zig for the shell layer so that Microsoft's COM-based TSF input method support would stay out of Zig, leaving only about 20 lines of Zig changed.", "body_md": "Ghostty is a terminal tool. It was made by Mitchell Hashimoto, the same person who made Vagrant and Terraform. It uses Zig language at its core. On Mac, it uses Swift. On Linux, it uses GTK. But there is no Windows version.\n\nI made a copy (forked it), and three days later, the Windows version was running. The first change (commit) was on September 1st at 2:30 AM. I released the first version on September 4th. The Windows part has 45 files and 48,000 lines of Rust code. I did not write a single line of code myself. There were 246 changes, and Claude (an AI) helped with every single one.\n\nThis post is about what I did during those three days.\n\nFirst, I need to explain why I copied this project. It wasn't just to get Ghostty on Windows. I did it to build an AI CLI (command-line) manager. This means using tools like Claude Code to manage and automate coding.\n\nThe idea is simple. You pick one AI to be the \"manager.\" This manager can see when other AI workers are stuck. It can read their screens, type for them, open new windows, and bring everyone into a group chat. There is also a task board to help them work together.\n\nThe Windows version was built using this exact system. I used my tool and AI to build the tool itself.\n\nThere was one AI manager and three or four AI workers. Each worker was a separate Claude session. The manager made a group chat, broke the work into smaller tasks, and put them on the board. Then, it gave tasks to the workers one by one. My job was to set the direction, make big choices, and watch them work.\n\nI made three rules for the AI:\n\n**1. Workers cannot touch git (the saving system).** They could not commit or add files. Only the manager could do that. Why? Once, the manager told me two workers didn't have conflicting code. It heard this from one of the workers. But another worker said there *was* a conflict! So I changed the rule: if you change something, you just say so. The manager must check for conflicts itself by looking at the files directly.\n\n**2. Two people cannot build the code at the same time.** The Zig builder deletes some files before remaking them. If someone else tries to build at the exact same time, it crashes. The first two times this happened, I wrongly thought the code was broken.\n\n**3. Do not touch programs you didn't start.** This sounds obvious. But sometimes, when the AI tries to \"clean up,\" it will close a window that you are still using!\n\nI didn't let the AI choose the coding language for the outside layer (the shell). That choice changes everything.\n\nFirst, I ruled out the Linux Zig code. It has 23,000 lines, which sounds helpful. But it is all GTK (a Linux window tool). The windows, tabs, and typing all use GTK. If I moved that to Windows, none of it would work.\n\nThe hardest part was actually the typing system (input method).\n\nWindows uses a typing system called TSF, built with something called COM. C++ and C# can use it easily. But Zig does not know how to use it. If you make one tiny mistake setting it up in Zig, the whole program crashes. Typing is the hardest part of a Windows terminal. And Chinese users need it within 10 seconds of starting the app.\n\nMac and Linux have easy ways to connect to their typing systems. Zig handles them well. But Windows uses COM.\n\nRust has official tools from Microsoft that support COM perfectly. By choosing Rust, all the typing code stays in Rust. Zig never has to touch COM.\n\nIn the end, I only changed about 20 lines of Zig code. The system info was squished into a small box. I just added a line for Windows, and that was it. Rust did all the rest of the work. Plus, the Mac version already did this, so we had an answer key to look at.\n\nNormally, you make the window show up first. Seeing it makes you feel good. But I told the AI to fix the typing system first.\n\nThis was the only part where I couldn't guess how hard it would be just by looking. If it was ten times harder than I thought, I needed to know before writing 20,000 lines of code.\n\nIt went very well. The typing code was over 400 lines. The very first time we built it and moved it to Windows, it worked! I typed `n i h a o`, the word box popped up, I pressed 1, and \"你好\" (hello) appeared.\n\nI still remember one line from the log file:\n\n```\nSetText 0..2 <- \"你\"\n```\n\nThis means \"Replace spaces 0 to 2 with '你'\". In computer memory, this is one letter. But on the screen, it takes up two spaces. If the computer thought it was 1 space, the word box would be in the wrong place. This only happens with Chinese, Japanese, and Korean text. English would look perfectly fine. These bugs are very hard to find.\n\nThe best part is that the AI didn't just guess the number \"2\". It asked the core program for the real size using the correct size chart. Seeing this made me trust this way of working.\n\nWe had a window, and typing worked. But when we connected the core program, the screen was completely black.\n\nIt didn't crash. It was just quietly black. Everything succeeded, and the window was getting messages to \"redraw,\" but there were zero pixels on the screen.\n\nLater, I found a pattern. If I made a small window and then dragged it bigger, it was black. But if I started it at the final big size right away, the picture showed up.\n\nI checked the logs and measured it:\n\n```\nPainted area = 1000x670      <- This was the size when the window was first made.\n```\n\nEven if I made the window bigger six times, this number never changed.\n\nBut the terminal itself was awake. If I typed a command in full screen to check the size, it said 143 columns and 41 rows. That perfectly matched the full screen size. The core program knew the right size.\n\nWe kept checking every step. The numbers were right at every step. Two AI workers wrote tests, and both failed. The screen was still black.\n\nThen we tried a new way. I told the AI to stop reading logs. Instead, it took a screenshot and used a drawing tool to find exactly where the black and white met.\n\nThe line was exactly at 1000. Everything was the right size, but only a rectangle the size of the *old* window was being drawn on the screen.\n\nI moved the log to the very last step, where the picture is actually put on the screen:\n\n```\nTarget=1240x790  Image area=1240x790  Canvas=1240x790\nDrawable area=(0,0,1000,670)          <- It died right here.\n```\n\nThe first three numbers updated, but the last one was stuck at the old size.\n\nI looked at the code and found the function that sets this area. I searched to see who used it. Zero results.\n\nThis \"drawable area\" was only set one time, right when the window was created. After that, no one ever updated it.\n\nWhy didn't Linux have this problem? I looked at the Linux code. GTK kindly resets this area for us every time the window gets bigger. Windows doesn't do that. It gives you a blank slate.\n\nThe worst part was that there was a note in the code about this! It said, \"No one does this on a bare window.\" The people who read that note didn't realize it was a bug.\n\nLooking back, starting with a big window only worked because the one-time setup matched the final size. The bug was always there; it just hid whenever the window was resized.\n\nThe hard part about AI coding isn't getting it to write. It's that the AI will confidently tell you \"the job is done\" or \"all tests passed\" when it isn't true.\n\nHere are two examples:\n\n**The file didn't even compile**\n\nWe added a new file. The compiler said \"0 errors, 0 warnings.\" It looked great.\n\nBut the line of code to actually include the file never ran. A chain of commands was running, and it broke in the middle. The rest of the commands were skipped. But the screen showed a \"success\" code (0) from the last command that *did* run. The AI thought everything was fine. I only found out later when the feature didn't work.\n\n**Broken checker scripts**\n\nThe AI wrote 16 test scripts. Four of them checked 0 files, printed \"Everything is fine,\" and gave a success code.\n\nTo a computer, \"I checked everything and found no problems\" looks exactly the same as \"I checked zero files.\"\n\nI found this by making the test scripts check an empty folder. If a script still said \"everything is fine,\" I knew it was broken.\n\nNow, every test must check itself first. If a specific line doesn't show up, it means the test didn't run. A test that never fails is worse than no test at all.\n\n**One more rule just for Windows**\n\nSome code only runs if you are *not* on a Mac. When building on a Mac, the computer sees this and ignores the code. It doesn't even check it for mistakes.\n\nSo, you can't just say, \"Run it on Mac to make sure it isn't broken.\"\n\nThe solution is to build it specifically for Windows. It might show errors for Windows, even if it looked fine on the Mac.\n\nAll these traps share one thing: they don't yell at you when they fail. And AI is really good at making things *look* right. So, I forced the AI to turn every trap we found into an automatic test.\n\nNow, there are 61 Python scripts in the `windows/tools/` folder. We run all of them before saving any code. The file names are the rules themselves:\n\n```\na-position-is-not-an-identity.py       (A position is not an identity)\nan-async-handle-is-not-copied.py       (An async handle is not copied)\nthe-ui-thread-never-waits-forever.py   (The UI thread never waits forever)\n```\n\nHere is why the first rule exists: Tabs and screen splits need permanent ID numbers. You can't just use their place in line (like \"number 2\"). Because tabs move around or close, \"number 2\" might silently become a different tab. It wouldn't crash, and it wouldn't leave a trace.\n\nOne habit was even better than the code itself. In our status notes, we never mix up two phrases:\n\nIf we don't have real numbers, we can't write \"verified,\" even if we are sure it works.\n\nAlso: If a note doesn't say \"tested,\" we assume it is NOT tested. We had to do it this way. Otherwise, people would assume that anything left blank was already tested.\n\nI couldn't test the Windows version on my Mac. I put a Windows computer next to me and controlled it from my Mac.\n\nThe AI logged in, installed packages, started the app, took screenshots, and read logs. I mostly just watched. But there were traps here, too.\n\nThe remote control tool types differently. It skips the normal typing system and forces the letters in. If you use it to test Chinese typing, you will think the typing system is broken. But if you send real keyboard presses, the word box pops up instantly.\n\nThe hardest part was making sure the window was in the front before pressing keys. Sometimes Windows quietly stops a window from coming to the front. The test wouldn't crash; it would just press keys into the wrong window. In the logs, \"keys didn't send,\" \"keys went to the wrong place,\" and \"the feature is broken\" all looked exactly the same.\n\nLater, I made a hard rule: Before sending a keystroke, the test must \"click the tested window.\" It cannot just use a command to \"bring window to front.\"\n\nWe tested the group chat feature using the group chat itself! I used a small local AI model as the manager on the test machine. It had three workers. They did a small project in an empty folder. They made files, opened terminals, made a chat, did the work, handed it in, and saved it. All five steps worked. The best part was watching the AI fix its own problem: when it was blocked by a password, it found out who it was, got permission, and tried again successfully.\n\nI released the first version on September 4th. Then, I spent a little over a week making it smoother. I gave it to testers on September 8th, and froze the code on September 9th. We really only wrote features for three days. The rest of the time was testing and cleaning up.\n\nThe core program has 72 actions. On Windows, we finished 63. I skipped 7 on purpose. There are 2 left to do: making the background see-through, and a quit timer.\n\nThe display part of the terminal wasn't copied or translated. It is the exact same Zig core. On Windows, I just gave it a window to live in. I only built the outside shell.\n\nWe built this in three days, but not because one AI wrote code really fast. It was because four AIs worked together the whole time. One worked on typing, one on tabs and splits, one fixed the black screen, and one added menus. That is how we got 139 updates so fast.\n\nDoing things at the same time is easy. The hard part is one human managing lots of AIs. You have to check: Who needs permission? Who finished 20 minutes ago and is just sitting there? Who needs you to check their design? I used to have to check everyone one by one. It broke my day into tiny pieces, and I couldn't get my own work done.\n\nSo, my idea was to give this manager job to another AI. The manager AI watches the screens of the worker AIs. It sees who stopped, who needs permission, and who needs a new task. I just talk to the manager at the beginning to plan things. After that, I leave them alone.\n\nThe group chat and task board aren't just for show. If the computer restarts or we clear the memory, they can start again the next day because the tasks are still on the board.\n\nWhat did I do for three days? I set the direction, made the rules, planned with the manager, and checked the final work.\n\nWhen working with AI, the hard work isn't getting it to write code. The hard work is building a test system so the AI can't trick you, and can't trick itself. The 61 checkpoints and the \"assume it's untested\" rule were all made for this.\n\nGhostty is a great terminal, and that is all thanks to the original creator. I only added the Windows part. This is not a rewrite. When the original Ghostty updates, this will update too. It uses the MIT license, just like the original.\n\n[https://github.com/Lugia123/polter](https://github.com/Lugia123/polter)\n\nThe Windows version is still very new. You can see what is tested and what isn't in `dev-docs/windows/status.md`.\n\nIf you find a problem, please open an issue in *my* repository. Do not send issues to the main Ghostty repository!", "url": "https://wpnews.pro/news/how-i-built-a-windows-version-of-ghostty-in-3-days-with-ai", "canonical_source": "https://dev.to/lugia123/how-i-built-a-windows-version-of-ghostty-in-3-days-with-ai-1d2j", "published_at": "2026-09-23 18:41:04+00:00", "updated_at": "2026-09-23 18:58:24.372239+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["Ghostty", "Mitchell Hashimoto", "Claude", "Claude Code", "Zig", "Rust", "Microsoft", "Windows"], "alternates": {"html": "https://wpnews.pro/news/how-i-built-a-windows-version-of-ghostty-in-3-days-with-ai", "markdown": "https://wpnews.pro/news/how-i-built-a-windows-version-of-ghostty-in-3-days-with-ai.md", "text": "https://wpnews.pro/news/how-i-built-a-windows-version-of-ghostty-in-3-days-with-ai.txt", "jsonld": "https://wpnews.pro/news/how-i-built-a-windows-version-of-ghostty-in-3-days-with-ai.jsonld"}}