<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[kdy1: The way I think]]></title><description><![CDATA[kdy1: The way I think]]></description><link>https://kdy1.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 15 Sep 2026 19:19:19 GMT</lastBuildDate><atom:link href="https://kdy1.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Programming Language in the AI era]]></title><description><![CDATA[These days, my preference is React and TypeScript for frontend development, and Go, TypeScript, then Rust for everything else.
I really like Rust, and I have used it a lot. But when I think about whic]]></description><link>https://kdy1.dev/2026-9-11-ai-and-programming-language</link><guid isPermaLink="true">https://kdy1.dev/2026-9-11-ai-and-programming-language</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Fri, 11 Sep 2026 02:02:47 GMT</pubDate><content:encoded><![CDATA[<p>These days, my preference is React and TypeScript for frontend development, and Go, TypeScript, then Rust for everything else.</p>
<p>I really like Rust, and I have used it a lot. But when I think about which language to choose for developing with AI, I rank Go highest. It seems to strike the best balance for building a fast verification loop.</p>
<h2>Bottlenecks in the iteration loop</h2>
<p>AI works incredibly fast. That means even a slightly slow part of the iteration loop can quickly become a bottleneck.</p>
<p>Developing with AI does not mean that AI’s working time—the time when the GPU is running—accounts for an overwhelmingly large share of the total. There are builds to wait for and tests to run.</p>
<p>This is the main reason I rank Go highest. It builds faster than Rust, and it runs things like tests faster than TypeScript. Its runtime performance is also pretty good considering how little time it takes to build.</p>
<p>Fast execution also means less time spent running tests. I also find that Go’s support for parallelism means test times grow much less sharply than they do with TypeScript as a project gets larger.</p>
<p>These days, human QA is the biggest bottleneck for me, and fast builds matter there too. When a build is slow during PR verification, QA slows down with it. Even as someone who likes Rust, I find its build times costly here.</p>
<h2>Rust’s checks are valuable, but expensive to pay for every time</h2>
<p>Rust catches a lot of problems. Its checks cover far more than Go’s <code>go test -race</code>. As I organize my thoughts for this post, I keep coming back to the feeling that Rust is both good and a bit excessive.</p>
<p>For most projects, I think running concurrency-related tests separately at regular intervals is enough. For something like a database, I would run them routinely, but that would mean shortening the interval. Running them every single time still seems wasteful to me.</p>
<p>The separate checks I have in mind are things like <code>go test -race</code>. I do not mean that these are equivalent to Rust’s compile-time checks. Rust catches much more.</p>
<p>But even recognizing the value of those checks, Rust builds are too slow for me to want to pay that cost every time. I do not think the frequency at which verification is needed always matches the frequency at which a build is needed.</p>
<h2>Even good type inference has a cost</h2>
<p>Rust’s type inference is a great feature. But when I wrote the code myself, I sometimes struggled to work out the types. I had to look through many files to figure out the right type.</p>
<p>Things were somewhat better when rust-analyzer worked well. But I developed in Rust before it existed, and even after it became available, it did not always work reliably. I still had to work without its help at times.</p>
<p>AI agents are so fast that I expect them to figure out types faster and better than I could. Even so, I do not feel that this gives me a reason to choose Rust when Go is available.</p>
<h2>How hard is it to get the performance?</h2>
<p>Rust is certainly a language with excellent performance. But using it well is much harder than people tend to think.</p>
<p>Imagine a senior developer unfamiliar with each language being asked by their company to build a highly parallel program in Go, TypeScript, and Rust. I would expect the Go version to be the fastest.</p>
<p>Handling system calls such as file reads efficiently is not particularly difficult in Go, but it is very difficult in Rust.</p>
<p>The difference, as I see it, is in how system calls are handled. Rust is designed to be usable in kernels, and that applies to async Rust as well. I see this design as part of what makes automatically inserting hooks around system calls difficult. Go, meanwhile, provides handling for system calls out of the box.</p>
<p>Unless a highly skilled developer designs the system properly, I think it is difficult to beat the handling Go already provides.</p>
<h2>Unfriendly error messages become an advantage</h2>
<p>Some of Go’s choices used to make me wonder why the language worked that way. In the AI era, I find those same choices helpful. Build error messages are one example.</p>
<p>Rust and TypeScript helpfully show code snippets in the terminal to explain what went wrong. Go tells you which line has which problem, and that is it. It does not even print every error. When I was coding by hand, I found this really frustrating.</p>
<p>But from an AI agent’s perspective, builds are fast, and the error messages do not consume much context either. The output I once found unfriendly has started to look like an advantage.</p>
<p>When developing with AI, I care about how quickly I can run the whole iteration loop, including builds, tests, and human QA. Right now, Go strikes the best balance for me.</p>
<hr />
<h2>Update: The options are limited</h2>
<p>This seemed so obvious that I forgot to mention it when writing this post, but you have to choose a language that AI writes well. That’s why this post focuses on Rust, Go, and TypeScript.</p>
]]></content:encoded></item><item><title><![CDATA[AI 시대의 프로그래밍 언어]]></title><description><![CDATA[요즘 내가 선호하는 언어는 프론트엔드에서는 React와 TypeScript, 그 외에는 Go, TypeScript, Rust 순이다.
나는 Rust를 정말 좋아하고 많이 써봤다. 그런데 AI와 개발할 때 어떤 언어를 고를지 생각하면, Go를 가장 높게 평가하게 된다. 검증 루프를 빠르게 구성하기에 균형이 가장 잘 맞는 것 같다.
이터레이션 루프의 병목
AI]]></description><link>https://kdy1.dev/2026-9-11-ai-and-programming-language-kr</link><guid isPermaLink="true">https://kdy1.dev/2026-9-11-ai-and-programming-language-kr</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Fri, 11 Sep 2026 02:02:45 GMT</pubDate><content:encoded><![CDATA[<p>요즘 내가 선호하는 언어는 프론트엔드에서는 React와 TypeScript, 그 외에는 Go, TypeScript, Rust 순이다.</p>
<p>나는 Rust를 정말 좋아하고 많이 써봤다. 그런데 AI와 개발할 때 어떤 언어를 고를지 생각하면, Go를 가장 높게 평가하게 된다. 검증 루프를 빠르게 구성하기에 균형이 가장 잘 맞는 것 같다.</p>
<h2>이터레이션 루프의 병목</h2>
<p>AI는 작업 속도가 엄청나게 빠르다. 그래서 이터레이션 루프의 구성 요소가 조금만 느려도 금방 병목이 된다.</p>
<p>AI로 개발한다고 해서 전체 작업 시간 중 AI가 일하는 시간, 그러니까 GPU가 돌아가는 시간의 비중이 엄청나게 높은 것은 아니다. 빌드를 기다리고, 테스트 실행을 기다려야 한다.</p>
<p>내가 Go를 가장 높게 평가하는 이유가 여기에 있다. Rust보다 빌드가 빠르고, TypeScript보다 테스트 같은 것들의 실행이 빠르다. 빌드 시간이 엄청나게 짧은 것에 비하면 런타임 성능도 괜찮다.</p>
<p>실행 속도가 빠르다는 것은 테스트를 돌리는 시간도 짧다는 뜻이다. Go는 병렬 처리가 잘 되는 언어라 프로젝트가 커질 때 테스트가 느려지는 정도도 TypeScript보다 훨씬 덜하다고 본다.</p>
<p>요즘은 사람이 수행하는 QA가 가장 큰 병목인데, 이것 역시 빌드가 빨라야 한다. PR을 검증할 때 빌드가 느리면 QA까지 느려진다. Rust를 좋아하는 나도 이 부분에서는 빌드 시간이 부담스럽다.</p>
<h2>Rust의 검증은 좋지만, 매번 부담하기에는 무겁다</h2>
<p>Rust는 많은 문제를 잡아준다. Go의 <code>go test -race</code>와 비교할 수 없을 정도로 검증 범위가 넓다. 글을 쓰려고 생각을 정리하다 보니, Rust가 좋으면서도 지나치다는 느낌이 든다.</p>
<p>내 생각에는 대부분의 프로젝트에서 동시성 관련 테스트는 주기적으로 따로 돌리면 충분하다. 데이터베이스 같은 프로젝트라면 일상적으로 돌리겠지만, 그것도 주기를 짧게 잡는 것이지 매번 돌리는 것은 낭비일 것 같다.</p>
<p>여기서 별도로 돌릴 검사로 생각한 것은 <code>go test -race</code> 같은 것들이다. 이것이 Rust의 컴파일 시 검증과 동등하다는 뜻은 아니다. Rust는 훨씬 많은 것을 잡아준다.</p>
<p>다만 그 검증의 가치를 인정하더라도, 매번 빌드할 때마다 비용을 부담하기에는 Rust의 빌드가 너무 느리다. 검증이 필요한 주기와 빌드가 필요한 주기가 꼭 같지는 않다고 본다.</p>
<h2>좋은 타입 추론에도 비용은 있다</h2>
<p>Rust의 타입 추론은 정말 좋은 기능이다. 하지만 사람인 내가 직접 코딩할 때도 타입을 파악하는 데 애먹었다. 올바른 타입을 알기 위해 살펴봐야 하는 파일이 많았다.</p>
<p>rust-analyzer가 잘 작동할 때는 그나마 나았다. 하지만 나는 그것이 없던 시절에도 개발했고, 생긴 뒤에도 항상 제대로 작동하는 것은 아니라 도움 없이 개발하기도 했다.</p>
<p>AI 에이전트는 워낙 빠르니 나보다 타입을 빨리 파악하고, 더 잘할 것이다. 그래도 Go를 두고 굳이 Rust를 고를 이유는 없는 것 같다.</p>
<h2>언어의 성능을 끌어내는 난이도</h2>
<p>Rust가 성능이 엄청나게 좋은 언어인 것은 맞다. 하지만 사람들이 생각하는 것보다 Rust를 제대로 쓰는 것은 훨씬 어렵다.</p>
<p>예를 들어 각 언어에 익숙하지 않은 시니어 개발자가 회사의 요구로 Go, TypeScript, Rust를 사용해 병렬 처리가 아주 많은 프로그램을 만든다고 해보자. 나는 Go로 만든 프로그램이 가장 빠를 것이라고 예상한다.</p>
<p>파일 읽기 같은 시스템 콜을 고성능으로 처리하는 것이 Go에서는 그리 어렵지 않지만, Rust에서는 정말 어렵기 때문이다.</p>
<p>내가 보는 차이는 시스템 콜을 다루는 방식에 있다. Rust는 커널에서도 쓸 수 있게 설계됐고, async Rust도 마찬가지다. 나는 이런 설계가 시스템 콜에 자동으로 훅을 넣는 처리를 어렵게 하는 배경이라고 본다. 반면 Go는 시스템 콜에 대한 처리를 기본으로 제공한다.</p>
<p>엄청난 실력자가 제대로 설계한 것이 아니라면, Go가 제공하는 처리를 직접 설계해서 이기기는 어렵다고 생각한다.</p>
<h2>불친절한 오류 메시지가 장점이 되다</h2>
<p>직접 코딩할 때는 Go가 왜 이런 선택을 했나 싶었던 부분도 AI 시대에는 도움이 된다. 빌드 오류 메시지가 그중 하나다.</p>
<p>Rust나 TypeScript는 터미널에 코드 스니펫까지 보여주면서 어떤 오류가 있는지 친절하게 알려준다. Go는 어느 줄에 어떤 문제가 있는지 알려주고 끝이다. 심지어 오류를 전부 출력하지도 않는다. 직접 코딩할 때는 정말 짜증났다.</p>
<p>그런데 AI 입장에서는 빌드도 빠른데 오류 메시지로 컨텍스트를 많이 쓰지도 않는다. 내가 불친절하다고 느꼈던 출력 방식이 오히려 장점으로 보이기 시작했다.</p>
<p>AI와 개발할 때는 빌드, 테스트, 사람이 수행하는 QA까지 포함한 이터레이션 루프를 얼마나 빠르게 돌릴 수 있는지가 중요하다. 지금 내게는 Go가 그 균형이 가장 잘 맞는 언어다.</p>
<hr />
<h2>Update: 선택지는 제한되어 있다</h2>
<p>너무 당연한 내용이라서 글 쓸 때 까먹었는데, AI가 잘 작성하는 언어 중에서 골라야 한다. 이 글이 Rust, Golang, TypeScript에 집중되어 있는 이유이다.</p>
]]></content:encoded></item><item><title><![CDATA[사람은 언제나 병목이다]]></title><description><![CDATA[나는 kdy1이라는 닉네임으로 활동하는 개발자다. 오픈소스 컴파일러 프로젝트인 SWC를 만들었고, 이전에는 Deno와 Vercel에서 일했다. 요즘은 Zephyr Cloud IO의 Labor0, Watchtower, theaiplatform.app을 함께 만들고 있다.
그중에서도 최근 가장 열심히 만드는 제품은 Labor0다. Labor0의 핵심 아이디어는]]></description><link>https://kdy1.dev/2026-9-6-humans-are-always-the-bottleneck-kr</link><guid isPermaLink="true">https://kdy1.dev/2026-9-6-humans-are-always-the-bottleneck-kr</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Sun, 06 Sep 2026 05:20:54 GMT</pubDate><content:encoded><![CDATA[<p>나는 <code>kdy1</code>이라는 닉네임으로 활동하는 개발자다. 오픈소스 컴파일러 프로젝트인 SWC를 만들었고, 이전에는 Deno와 Vercel에서 일했다. 요즘은 Zephyr Cloud IO의 <a href="https://labor0.com">Labor0</a>, <a href="https://github.com/ZephyrCloudIO/watchtower">Watchtower</a>, <a href="http://theaiplatform.app/">theaiplatform.app</a>을 함께 만들고 있다.</p>
<p>그중에서도 최근 가장 열심히 만드는 제품은 Labor0다. Labor0의 핵심 아이디어는 간단하다.</p>
<p><strong>AI가 작업을 주도한다.</strong></p>
<p>인간은 언제나 병목이다. 이것이 내 철칙이다.</p>
<p>물론 HITL(Human in the Loop)은 필수다. 제품과 기술에 관한 결정은 사람이 내려야 하고, PR도 사람이 최종 검토하고 머지해야 한다. 다만 AI가 작업하는 동안 사람을 호출하는 횟수는 가능한 한 줄여야 한다.</p>
<p>Labor0는 사람의 결정이 필요할 때 웹 푸시 등으로 질문을 보낸다. 사람이 한 PR을 검토하고 있는 동안에는 다음 작업을 자동으로 시작한다. 사람이 모든 과정에 계속 붙어 있는 대신, 꼭 필요한 결정 지점에만 참여하는 구조다.</p>
<h2>AI에게 일을 시킬 때의 제1원칙</h2>
<p>내가 AI에게 일을 시킬 때 가장 중요하게 생각하는 원칙은 언제나 같다.</p>
<p><strong>작업을 충분히 작은 단위로 쪼개야 한다.</strong></p>
<p>한 PR이 너무 크면 사람도 리뷰하기 어렵고 AI도 리뷰하기 어렵다. 결국 리뷰 품질이 떨어진다.</p>
<p>나는 AI가 등장하기 전부터 코드 리뷰를 아주 많이 했다. GitHub에 기록된 PR 리뷰 횟수가 1년에 5천 번을 넘을 정도였다. 그 경험을 통해 큰 PR에서는 제대로 된 리뷰가 어렵다는 것을 배웠다.</p>
<p>AI도 비슷하다. 컨텍스트 크기 같은 이유가 영향을 주는 것 같지만, 정확한 원인과 별개로 작업이 커질수록 결과를 검증하기 어려워지는 것은 분명하다.</p>
<p>유지 가능한 바이브 코딩을 하려면 작업을 나누는 과정이 필수다. Labor0는 이 과정을 플랫폼으로 만든다. 작업이 크면 여러 작업과 PR로 나누고, 작업 사이에 의존성이 있다면 그 순서에 맞춰 실행한다. 서로 독립적인 작업은 병렬로 진행한다.</p>
<p>다만 이렇게 많은 작업을 병렬로 돌리려면 입력부터 상세해야 한다. 내가 수십 개의 세션을 동시에 운영할 수 있는 중요한 이유도 이슈와 PRD가 매우 구체적이기 때문이다.</p>
<p>여기서 내가 항상 사용하는 것이 <a href="https://github.com/kdy1/kdy1-scripts/tree/f9079364d84a9d3e1eaa1c8b8c9dbc60ee4e46de/skills/add-issue">$add-issue</a>와 <a href="https://github.com/kdy1/kdy1-scripts/tree/f9079364d84a9d3e1eaa1c8b8c9dbc60ee4e46de/skills/write-prd">Write PRD</a> 스킬이다.</p>
<p><code>$add-issue</code>는 확인된 버그를 재현 사례와 구현 범위가 포함된 상세한 GitHub 이슈로 만든다. Write PRD를 사용하면 필요한 결정을 둘러싼 대화가 이어진다. 결정은 모두 내가 직접 내리지만, 큰 프로젝트의 규모와 비교하면 사람이 내려야 하는 결정은 생각보다 많지 않다. 물론 프로젝트 자체가 크기 때문에 결정 횟수의 절대적인 수가 작은 것은 아니다.</p>
<h2>오늘 첫 번째 작업: Labor0</h2>
<p>오늘은 먼저 Labor0를 위한 PRD 세 개를 작성했다. PRD를 작성할 때는 GPT 5.6 Sol을 사용한다.</p>
<p>첫 번째는 Devin의 Knowledge 시스템과 비슷한 자동 학습 기능에 관한 PRD였다. 두 번째는 데스크탑과 Apple Watch 앱에서 사용할 음성 조작 기능에 관한 것이었다. 세 번째는 Slack과 Discord 연동을 강화하기 위한 비교적 큰 리팩터링이었다. Labor0의 작업 상태가 해당 메신저의 해당 스레드 안에서 자연스럽게 갱신되도록 만드는 작업이다.</p>
<p>이 가운데 메신저 연동 개선은 구현을 시작했고, 나머지 두 개는 아직 PRD만 작성했다.</p>
<p>Labor0는 24시간 돌아가는 소프트웨어 팩토리에 가깝다. 실제로 Labor0 자체 개발을 위한 세션만 평소 약 10개가 계속 실행되며 토큰을 사용한다. 그만큼 비용도 상당하다. 그래서 필요성이 확정된 작업부터 구현을 시작한다.</p>
<p>PRD는 단순한 문서가 아니다. 무엇을 만들지 결정하고, 아직 만들 필요가 없는 작업에는 토큰을 쓰지 않도록 경계를 세우는 역할도 한다.</p>
<h2>두 번째 작업: Watchtower</h2>
<p>그다음에는 Watchtower를 작업했다.</p>
<p>Watchtower는 Sentry와 호환되는 에러 모니터링 및 관측 시스템으로, 현재 Build in Public 방식으로 개발하고 있다. Watchtower 역시 Labor0와 마찬가지로 스펙 주도 개발 방식을 사용하며 Codex 자동 리뷰를 적극적으로 활용한다.</p>
<p>오늘은 Write PRD 스킬을 사용해 <a href="https://github.com/ZephyrCloudIO/watchtower/issues/13">Watchtower 이슈 #13</a>을 작성했다.</p>
<p>PRD를 만드는 과정에서 통신 방식에 관한 결정을 내려야 했다. 나는 강타입을 좋아해서 Connect RPC를 사용하고 싶었다. 그런데 대화 과정에서는 JSON과 HTTP 기반으로 가는 것이 권장됐다.</p>
<p>몇 차례 더 대화하면서 심각한 기술적 제약이 있는지 확인했다. 살펴보니 본질적인 문제라기보다 Protobuf와 관련 도구의 Rust 지원이 주된 이유였다. 그래서 필요한 것이 있다면 우리가 직접 구현하기로 하고, Protobuf와 HTTP를 사용하기로 결정했다. Connect는 스트리밍이 필요해지는 시점에 도입하기로 했다.</p>
<p>완성된 PRD는 Labor0를 통해 <a href="https://github.com/ZephyrCloudIO/watchtower/pull/31">PR #31</a>로 바뀌었고, 오늘 바로 검토해서 머지했다.</p>
<h2>세 번째 작업: SWC</h2>
<p>마지막으로 SWC를 작업했다.</p>
<p>Codex 데스크탑 앱에서 GPT 6 Astra를 사용해 ES minifier의 모든 파일을 검사하도록 요청했다. 실제로 사용한 프롬프트는 다음과 같다.</p>
<blockquote>
<p>서브에이전트 적극적으로 활용해. ES minifier의 모든 파일 검사해줘. 할 일은 각 파일의 모든 분기 따져보고 각각에 대해서 혹시나 잘못된 조건이 있는지 검사하고, 혹시나 잘못된 조건이 있으면 예시 입력값 만들어서 $add-issue 해주는거야</p>
</blockquote>
<p>Codex는 버그를 발견하면 <code>$add-issue</code> 스킬을 사용해 GitHub 이슈로 남겼다. 그 결과 오늘 하루 동안 46개의 버그를 찾았다.</p>
<p>그다음 <a href="https://labor0.com">Labor0</a>에 한 문장을 입력했다.</p>
<blockquote>
<p>오늘 kdy1이 만든 모든 es/minifier 이슈들 고쳐줘</p>
</blockquote>
<p>46개 이슈는 45개의 Labor0 작업이 됐다. 스코프상 두 이슈를 함께 처리하는 편이 적절하다고 판단된 작업이 하나 있었기 때문이다.</p>
<p>45개 작업은 완전히 병렬로 실행됐고, 각각 하나의 PR을 만들어 총 45개의 PR이 열렸다. 작업이 진행되는 동안 나는 상황을 계속 지켜보지 않았다. 폰을 보고 게임을 하며 놀고 있어서, 모든 PR이 열리기까지 정확히 얼마나 걸렸는지도 모른다.</p>
<p>버그 탐색에는 GPT 6 Astra를 사용했지만, 45개 구현 작업을 전부 Astra가 처리한 것은 아니다. 간단한 작업이 많았기 때문에 GPT 5.6 Terra가 많이 사용됐고, 어려운 Premium 작업에는 Astra가 배정됐다.</p>
<p>이 글을 쓰는 시점에는 45개 PR 중 21개가 머지됐다. 닫아야 했던 PR은 없었다. 나머지는 Codex 자동 리뷰와 Labor0의 피드백 반영 루프를 기다리거나, 실패한 CI를 수정하는 중이다.</p>
<p>머지에 필수인 CI가 실패하면 Labor0가 수정한다. 머지 충돌이 생겨도 Labor0가 해결하고 다시 가져온다.</p>
<h2>Codex의 <code>+1</code>을 기다린다</h2>
<p>내가 작업하는 모든 저장소에는 공통된 리뷰 과정이 있다. SWC뿐 아니라 Labor0와 Watchtower에도 같은 방식을 사용한다.</p>
<p>Labor0가 수정 커밋을 올리면 Codex가 다시 자동 리뷰한다. 리뷰 항목이 발견되면 Labor0가 피드백을 반영하고 새 커밋을 올린다. 그러면 Codex가 그 커밋을 다시 리뷰한다.</p>
<p>이 과정은 Codex가 더 이상 리뷰 항목을 찾지 못할 때까지 반복된다. 충분히 리뷰했다고 판단하면 Codex는 PR에 <code>+1</code> 이모지를 남긴다.</p>
<p>저장소 설정에서는 <code>chatgpt-codex-connector</code> 봇이 남긴 리뷰를 자동으로 반영하도록 구성해 두었다. 이 설정 덕분에 리뷰가 나올 때마다 내가 승인하지 않아도 Labor0가 바로 후속 작업을 진행한다. 해당 설정이 없다면 Labor0는 프로젝트 권한이 있는 사람에게 리뷰를 반영할지 물어본다.</p>
<p>나는 <code>+1</code>이 달린 PR만 필터링해 최종 검토 대상으로 본다. 세션을 많이 실행하고 PR을 많이 만들다 보니, 이런 필터가 없으면 검토할 준비가 끝난 PR을 찾는 일 자체가 또 하나의 일이 된다.</p>
<p>SWC에서 머지한 21개 PR도 모두 Codex가 <code>+1</code>을 남긴 뒤 GitHub에서 코드를 직접 검토하고 수동으로 머지했다.</p>
<h2><code>+1</code>은 사람의 승인이 아니다</h2>
<p>Codex가 <code>+1</code>을 남겼다고 해서 PR이 항상 올바른 것은 아니다. 정말 이상한 상태의 PR에도 <code>+1</code>이 달릴 때가 있다.</p>
<p>Codex는 내가 처음에 품었던 의도를 전부 알지 못한 채 리뷰를 시작한다. 그래서 코드만 보고 판단한 결과가 PR의 방향을 완전히 바꾸기도 한다.</p>
<p>한번은 내가 cut-over 마이그레이션을 하려던 PR이 있었다. Codex는 롤백하면 앱 데이터가 망가질 수 있다고 판단했고, 그 우려에 따라 PR의 방향을 바꿨다. Codex가 <code>+1</code>을 남겼을 때에는 그 잘못된 리뷰 내용이 Labor0를 통해 이미 PR에 반영된 상태였다.</p>
<p>나는 최종 검토에서 “이 작업은 cut-over이며 롤백하지 않을 것이니 해당 변경을 되돌려라”라는 리뷰를 남겼다. Labor0가 이를 반영했고, PR은 정상적으로 머지됐다.</p>
<p>그래서 <code>+1</code>은 머지해도 된다는 최종 승인이 아니다. 자동 리뷰 루프가 끝났고 사람이 검토할 차례가 됐다는 신호에 가깝다.</p>
<p>최종 리뷰에서는 AI가 테스트를 지나치게 추가하지 않았는지도 확인한다. AI가 스크립트의 텍스트를 하나씩 매칭하는 식으로 과도하고 취약한 테스트를 만들 때가 있기 때문이다. SWC PR에서는 Codspeed를 확인해 성능 문제가 생기지 않았는지도 꼭 본다.</p>
<p>AI는 자잘한 버그를 자동 리뷰 단계에서 많이 잡아준다. 덕분에 내가 받는 인지 부하는 예전에 혼자 모든 것을 검토하던 때보다 훨씬 작다. 그럼에도 원래 의도, 테스트의 적절성, 성능과 최종 머지 결정은 사람이 책임져야 한다.</p>
<p>토큰을 잔뜩 쓴 PR을 최종적으로 닫아야 할 때도 있다. 그래도 전체적인 효율은 지금 방식이 훨씬 높다. 엄청나게 빠르기 때문이다.</p>
<h2>6개 세션에서 50개가 넘는 세션으로</h2>
<p>예전에는 Claude Code CLI를 사용했다. 당시에는 세션을 6개만 돌려도 매우 피곤했다. 터미널은 원래 병렬 작업을 관리하기 위해 만들어진 UI가 아니어서, 여러 세션 사이를 계속 전환해야 했다.</p>
<p>Codex 데스크탑으로 옮긴 뒤에는 약 15개 세션까지 운영할 수 있었다.</p>
<p>지금은 특정 시점에 Labor0 세션이 50개를 넘어갈 때도 있지만 크게 힘들지 않다. SWC 작업 45개가 병렬로 실행되던 시점에는 Labor0 자체 개발 세션도 약 15개가 돌아가고 있었을 것이다. 두 프로젝트만 합쳐도 동시에 실행되거나 관리 중인 세션이 50개를 넘는다.</p>
<p>그 외에도 <code>typescript-go</code>를 <code>typescript-rust</code>로 포팅하는 프로젝트, Turborepo를 Effect 기반 TypeScript로 포팅하는 프로젝트, 여러 개인 프로젝트를 함께 진행하고 있다.</p>
<p>이 정도의 동시 작업이 가능한 것은 세션 수 자체보다 각 작업의 범위와 결정 사항이 상세하게 정리돼 있기 때문이다. <code>$add-issue</code>와 Write PRD가 큰 역할을 한다.</p>
<h2>작업에 맞춰 모델을 배분한다</h2>
<p>Labor0에서는 작업마다 하네스와 모델을 직접 지정하거나, AI가 난이도에 맞춰 선택하도록 설정할 수 있다. Cheap, Standard, Premium은 난이도에 따라 작업을 배분하기 위한 개념이다.</p>
<p>나는 Codex 리뷰 반영 작업을 Standard 티어로 실행한다. 여기에는 GPT 5.6 Terra나 GLM 5.3을 사용한다. CI 실패 수정과 머지 충돌 해결은 Cheap 티어로 두고 GPT 5.6 Luna의 high 또는 xhigh를 사용한다.</p>
<p>Premium 티어는 공급자에 따라 다르게 구성한다. Codex 기본 공급자에서는 GPT 5.6 Sol을 사용하고, OpenRouter를 연결한 Codex Custom Provider에서는 GLM 5.3 xhigh를 사용한다. SWC 프로젝트에서는 오늘 Astra를 써보고 싶어서 Premium 작업을 GPT 6 Astra로 설정했다.</p>
<p>Labor0와 Watchtower의 하네스도 Codex다. 다만 Labor0의 Codex Custom Provider 기능으로 OpenRouter에 연결하기 때문에 GLM 모델을 선택할 수 있다. 구현 작업은 GPT 5.6 Terra에서 더 가성비가 좋은 GLM 5.3으로 옮기는 중이다. Codex 데스크탑에서 직접 처리하는 잡다한 작업에는 GPT 5.6 Terra를 사용한다.</p>
<p>Labor0 세션은 AWS ECS Fargate의 격리된 환경에서 실행된다. 내 보안 모델에서는 이 격리 덕분에 오픈 모델 사용이 큰 위협이 되지 않는다고 본다.</p>
<p>모든 작업에 가장 비싼 모델을 사용할 필요는 없다. 작업을 충분히 작게 나누고, 상세한 스펙을 주고, 난이도에 맞는 모델을 배정하고, 자동 리뷰 루프를 거치게 하는 것이 내가 현재 사용하는 방식이다.</p>
<h2>이 글은 현재 시점의 기록이다</h2>
<p>Labor0가 더 발전하면 내 개발 프로세스는 또 바뀔 것이라고 생각한다. 그래서 지금의 방식을 기록해두고 싶었다.</p>
<p>나는 AI Native에 가까운 개발자로서 얻은 인사이트를 공유하고 싶다. 원래부터 사람을 돕고 지식을 나누는 일을 좋아했다. 어릴 때부터 후배들을 가르치고 코드를 리뷰하는 일을 많이 했다.</p>
<p>많은 사람이 이 글을 보고 AI 시대에 맞게 개발하는 방법을 각자의 환경에서 배울 수 있었으면 좋겠다.</p>
<p>지금 내가 도달한 결론은 명확하다.</p>
<p>사람은 언제나 병목이다. HITL은 필수지만, 루프에서 사람을 호출하는 횟수는 최소화해야 한다. AI가 작업을 주도하고, 사람은 의도와 결정을 책임진다. 그리고 이 방식을 유지 가능하게 만드는 첫 번째 조건은 언제나 같다.</p>
<p><strong>작업을 충분히 작은 단위로 쪼개는 것.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Humans Are Always the Bottleneck]]></title><description><![CDATA[Today, a single prompt turned 46 SWC issues into 45 pull requests.
I do not know exactly how long it took. While the work was running, I was looking at my phone and playing games. By the time I checke]]></description><link>https://kdy1.dev/2026-9-6-humans-are-always-the-bottleneck</link><guid isPermaLink="true">https://kdy1.dev/2026-9-6-humans-are-always-the-bottleneck</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Sun, 06 Sep 2026 05:20:43 GMT</pubDate><content:encoded><![CDATA[<p>Today, a single prompt turned 46 SWC issues into 45 pull requests.</p>
<p>I do not know exactly how long it took. While the work was running, I was looking at my phone and playing games. By the time I checked, the work had already moved on without me.</p>
<p>My name is kdy1. I created SWC, an open-source compiler project, and previously worked at Deno and Vercel. These days, I spend a lot of time building <a href="https://labor0.com">Labor0</a>, along with <a href="https://github.com/ZephyrCloudIO/watchtower">Watchtower</a> and <a href="http://theaiplatform.app/">theaiplatform.app</a> at Zephyr Cloud IO.</p>
<p>My development workflow has changed dramatically over the past few years. I used to do nearly everything myself. Now, almost all of my development work involves Labor0 and Codex automatic review.</p>
<p>I have one rule that shapes the entire process:</p>
<blockquote>
<p>Humans are always the bottleneck.</p>
</blockquote>
<p>Human-in-the-loop is still essential. Decisions must be made by a human, and I still review and merge pull requests myself. But I want the AI to drive the work and call me into the loop as few times as possible.</p>
<p>When Labor0 encounters a decision, it can send me a question through something like a web push notification. Once I answer, it continues. When a PR is ready, I review and merge it, but Labor0 does not need to wait for me before starting the next independent task.</p>
<h2>A day of AI-native development</h2>
<p>On the day I wrote this, I worked on Labor0, Watchtower, and SWC, in that order.</p>
<p>For Labor0, I wrote three PRDs:</p>
<ul>
<li><p>An automatic learning feature similar to Devin’s Knowledge system</p>
</li>
<li><p>Voice controls for desktop and Apple Watch apps</p>
</li>
<li><p>A large refactoring of Labor0’s messenger integration, so that status updates appear naturally inside the corresponding Slack or Discord thread</p>
</li>
</ul>
<p>I started implementing the messenger refactoring. The other two remain PRDs for now.</p>
<p>I always use my <a href="https://github.com/kdy1/kdy1-scripts/tree/f9079364d84a9d3e1eaa1c8b8c9dbc60ee4e46de/skills/write-prd">Write PRD skill</a> when writing a PRD. I use GPT 5.6 Sol for this work.</p>
<p>The skill does not make decisions for me. I make every decision myself. What surprised me, though, is how few decisions I need to make relative to the size of the projects I run. The absolute number is not small because these projects are large, but it is still much lower than I would have expected.</p>
<p>The skill turns PRD writing into a conversation. It identifies decisions that need to be made, and I resolve them.</p>
<p>I saw this clearly while working on Watchtower, a Sentry-compatible error monitoring and observability system that I recently started building in public. Watchtower uses a spec-driven development process.</p>
<p>While writing <a href="https://github.com/ZephyrCloudIO/watchtower/issues/13">PRD #13</a>, I initially wanted to use Connect RPC because I prefer strongly typed systems. JSON over HTTP was recommended instead, so I spent a few rounds of conversation checking whether Connect had a serious limitation.</p>
<p>It turned out that the main concern was Rust support for Protobuf and its surrounding tooling. That was not a fundamental constraint for me. I decided that we could implement what we needed if necessary, so the PRD settled on Protobuf over HTTP. I considered introducing Connect immediately, but decided to wait until we actually need streaming.</p>
<p>That decision was captured in the PRD. Labor0 then turned the PRD into <a href="https://github.com/ZephyrCloudIO/watchtower/pull/31">PR #31</a>, which I reviewed and merged that day.</p>
<p>After Watchtower, I moved on to SWC.</p>
<h2>Finding 46 bugs with GPT 6 Astra</h2>
<p>I wanted to try GPT 6 Astra, so I opened Codex Desktop and gave it this prompt:</p>
<blockquote>
<p>Use subagents aggressively. Inspect every file in the ES minifier. For each file, examine every branch and check whether any condition is incorrect. If you find an incorrect condition, construct an example input and use <a href="https://github.com/kdy1/kdy1-scripts/tree/f9079364d84a9d3e1eaa1c8b8c9dbc60ee4e46de/skills/add-issue">$add-issue</a> to report it.</p>
</blockquote>
<p>This produced 46 GitHub issues.</p>
<p>The <code>$add-issue</code> skill is an important part of the process. A large number of parallel sessions is only useful when every session receives a sufficiently detailed issue. The skill ensures that a bug is supported by evidence and turned into a self-contained, implementation-ready GitHub issue.</p>
<p>Once the issues existed, I gave Labor0 one prompt:</p>
<blockquote>
<p>Fix every es/minifier issue created by kdy1 today.</p>
</blockquote>
<p>Labor0 converted the 46 issues into 45 tasks. One task covered two issues because their scopes belonged together. A Labor0 task generally produces one PR, so those 45 tasks became 45 pull requests.</p>
<p>All 45 tasks ran in parallel. When tasks depend on each other, Labor0 runs them in dependency order, but these tasks could run concurrently.</p>
<p>GPT 6 Astra found the bugs, but it did not implement all 45 fixes. Labor0 assigns models according to task difficulty, and many of these fixes were simple enough to use GPT 5.6 Terra. I had configured the SWC project to use GPT 6 Astra for difficult, Premium-tier work.</p>
<p>At the time of writing, I had merged 21 of the 45 PRs. I had not closed any of them. The remaining PRs were still going through the Codex review loop or waiting for CI failures to be repaired. When a required CI check fails, Labor0 starts work to fix it. It also repairs merge conflicts when they appear.</p>
<h2>AI reviews AI until it runs out of comments</h2>
<p>Codex automatic review is part of my workflow across every repository, including SWC, Watchtower, and Labor0 itself.</p>
<p>Each time Labor0 pushes a fix commit, Codex reviews the PR again. If Codex finds another issue, Labor0 applies the feedback and pushes another commit. Codex reviews that commit, and the loop continues.</p>
<p>When Codex can no longer find another review item, it leaves a <code>+1</code> reaction on the PR.</p>
<p>I configured these repositories so that reviews from the <code>chatgpt-codex-connector</code> bot are applied automatically. Without that setting, Labor0 asks someone with permission on the Labor0 project whether it should apply the review.</p>
<p>Because I run so many sessions and create so many PRs, the <code>+1</code> reaction is also my review queue. I filter for PRs that received a <code>+1</code> and review only those. Otherwise, finding which PRs are ready for final review would itself become a job.</p>
<p>The 21 SWC PRs I merged all went through this process. Codex left a <code>+1</code>, I inspected the code directly on GitHub, and then I merged each PR manually.</p>
<p>This reduces my cognitive load considerably. Codex catches many small bugs before I ever see the code. Compared with reviewing everything alone, I can focus on fewer and more important questions.</p>
<p>But a <code>+1</code> does not mean the PR is correct. It means that Codex has run out of review comments.</p>
<h2>Why a human still has to look</h2>
<p>Codex begins its review without fully knowing my intent. Because of that, it can sometimes push a PR in a completely wrong direction.</p>
<p>In one migration PR, I wanted a cut-over migration with no rollback path. Codex was concerned that rolling the migration back could damage application data, so it changed the direction of the PR. Labor0 automatically applied that review, and Codex eventually left a <code>+1</code> on the result.</p>
<p>When I performed the final review, the PR was no longer implementing what I wanted.</p>
<p>I left another review explaining that this was a cut-over migration, that we would not roll it back, and that the previous change should be reverted. Labor0 applied my feedback, and the PR was eventually merged successfully.</p>
<p>I also check whether the AI has added too many tests. It tends to go too far. I have seen it add tests that inspect a script by matching its text piece by piece. Tests like that add volume without necessarily improving confidence.</p>
<p>For SWC, I also use Codspeed during final review and always check whether a change introduces a performance problem.</p>
<p>My final review therefore focuses on things that automatic review can still miss:</p>
<ul>
<li><p>Whether the implementation matches the original intent</p>
</li>
<li><p>Whether the PR has moved in an unnecessary or incorrect direction</p>
</li>
<li><p>Whether the tests are excessive or overly tied to implementation details</p>
</li>
<li><p>Whether an SWC change causes a performance regression</p>
</li>
<li><p>Whether I am personally willing to merge the result</p>
</li>
</ul>
<p>The AI can remove a great deal of routine review work, but the final judgment still belongs to me.</p>
<h2>Small tasks are the foundation</h2>
<p>My first rule when assigning work to AI is to split it into sufficiently small units.</p>
<p>Large PRs are difficult for both humans and AI to review, and review quality falls as the PR grows. Before AI coding tools existed, GitHub recorded more than 5,000 PR reviews from me in a single year. After doing that much review, I came to believe very strongly in keeping PRs small.</p>
<p>I suspect AI review quality also declines partly because of context size, although that is my guess rather than a confirmed explanation.</p>
<p>When Labor0 receives a large task, it breaks the work into smaller tasks and produces multiple PRs. It also represents dependencies between those tasks and executes them in the required order.</p>
<p>Detailed issues and PRDs make this possible. <code>$add-issue</code> produces well-defined bug reports. Write PRD helps me resolve decisions before implementation begins. Labor0 can then distribute those bounded tasks across many sessions.</p>
<p>This is the process I think maintainable vibe coding requires: small tasks, explicit decisions, detailed handoffs, independent PRs, and continuous review. Labor0 is my attempt to turn that process into a platform.</p>
<h2>From six terminal sessions to more than fifty</h2>
<p>I used Claude Code CLI in the past. Even six concurrent sessions made me very tired because a terminal was not designed as a UI for managing parallel work. I had to keep switching between sessions.</p>
<p>After moving to Codex Desktop, I could manage around 15 sessions.</p>
<p>With Labor0, there are moments when I have more than 50 sessions active across my projects without feeling particularly overwhelmed. Labor0 development alone commonly keeps around ten sessions consuming tokens around the clock. During the SWC run, I think Labor0 itself had around 15 active sessions. Combined with the SWC work, those two projects alone pushed the total beyond 50.</p>
<p>And those are not my only projects. I also run projects that port <code>typescript-go</code> to <code>typescript-rust</code>, port Turborepo to Effect-based TypeScript, and work on various personal projects.</p>
<p>The number of sessions is possible because the issues are detailed and the work is divided. Concurrency without clear scope would only create more things for me to untangle.</p>
<h2>Routing models by cost and difficulty</h2>
<p>Labor0 can assign a harness and model directly for each task, or allow AI to select them according to difficulty. Cheap, Standard, and Premium are the tiers Labor0 uses for this routing.</p>
<p>I currently use the Standard tier for applying review feedback, with GPT 5.6 Terra or GLM 5.3. CI repairs and merge-conflict resolution use the Cheap tier, with GPT 5.6 Luna at high or xhigh reasoning.</p>
<p>With the regular Codex provider, my Premium tier uses GPT 5.6 Sol. When I connect OpenRouter through Labor0’s Codex Custom Provider, Premium uses GLM 5.3 xhigh. Labor0 and Watchtower both use the Codex harness with this Custom Provider setup, which is how I can select GLM models.</p>
<p>For implementation through Labor0, I have been moving from GPT 5.6 Terra toward the more cost-efficient GLM 5.3. When I work directly in Codex Desktop, I still use GPT 5.6 Terra for miscellaneous tasks.</p>
<p>Labor0 sessions run in isolated environments on AWS ECS Fargate. In my security model, that isolation means using open models presents relatively little additional threat.</p>
<p>Cost still matters. Labor0 feels like a software factory that can operate 24/7, and a factory running 24/7 consumes a lot of tokens. That is why I do not immediately implement every PRD. Once I know that a feature is necessary, I start implementation. Until then, the detailed PRD is enough.</p>
<h2>This workflow will change again</h2>
<p>I expect my development process to change again as Labor0 improves. That is one reason I wanted to write this down: it is a record of how I work at this particular moment.</p>
<p>I also want to share what I have learned as someone getting closer to being an AI-native developer. I have always enjoyed helping people and sharing what I know. Since I was young, I have taught junior developers, reviewed their code, and helped in whatever ways I could.</p>
<p>I hope more developers can look at workflows like this and learn how development can work in this era.</p>
<p>The human is still the bottleneck. My goal is to spend that limited human attention on decisions, intent, and final judgment—and let the software factory handle as much of the rest as possible.</p>
]]></content:encoded></item><item><title><![CDATA[Labor0를 만든 이유]]></title><description><![CDATA[사람이 병목이 되는 횟수를 줄이기
AI 에이전트 세션을 많이 실행하는 것 자체는 어렵지 않다. 내가 해결하고 싶었던 문제는 세션이 늘어날 때마다 사람이 모든 진행 상황을 확인하고 관리해야 한다는 점이었다.
사람을 병목에서 완전히 빼는 게 목표는 아니다. 사람은 여전히 병목이다. 대신 사람이 개입해야 하는 횟수를 줄이고, 실제로 결정이 필요한 순간에만 사람을]]></description><link>https://kdy1.dev/2026-8-22-labor0</link><guid isPermaLink="true">https://kdy1.dev/2026-8-22-labor0</guid><category><![CDATA[Labor0]]></category><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Sat, 22 Aug 2026 14:07:50 GMT</pubDate><content:encoded><![CDATA[<h2>사람이 병목이 되는 횟수를 줄이기</h2>
<p>AI 에이전트 세션을 많이 실행하는 것 자체는 어렵지 않다. 내가 해결하고 싶었던 문제는 세션이 늘어날 때마다 사람이 모든 진행 상황을 확인하고 관리해야 한다는 점이었다.</p>
<p>사람을 병목에서 완전히 빼는 게 목표는 아니다. 사람은 여전히 병목이다. 대신 사람이 개입해야 하는 횟수를 줄이고, 실제로 결정이 필요한 순간에만 사람을 부르는 구조를 만드는 게 중요하다.</p>
<p>나는 이를 위한 작업 체계를 만들었고, 그 중심에 <a href="https://labor0.com">Labor0</a>가 있다. 한때 50개가 넘는 세션이 돌아갈 때도 내가 느끼는 인지 부하는 거의 없었다.</p>
<p>큰 작업을 맡기면 AI가 먼저 적당한 크기로 나누고, 나뉜 작업의 진행을 주도했다. 작업을 적절한 크기로 나누는 것은 중요하다. PR이 너무 크면 제목만 보고 내용을 파악하기 어렵고, 사람이 직접 리뷰하기도 힘들다. Codex 자동 리뷰의 성능도 떨어진다.</p>
<p>나는 작업이 진행되는 모든 과정에 개입하지 않았다. 결정이 필요한 순간에만 호출받았고, Labor0의 PR 처리와 Codex 자동 리뷰가 모두 끝난 뒤에 PR을 확인했다.</p>
<p>여기서 PR 처리란 CI 실패 수정, 머지 충돌 해결, 리뷰 내용 반영을 말한다. 일부 PR은 나도 코드를 직접 리뷰했다.</p>
<p>안정성을 확보하기 위해 자동화 테스트에도 집중했다. 자동화 테스트에 빈틈이 없는지는 Codex를 활용해 다시 검증했다.</p>
<h2>에이전트가 이어서 일할 수 있는 이슈 만들기</h2>
<p>모든 프로젝트에서는 GitHub를 이슈 관리 도구로 사용했다. 이슈를 만드는 일도 에이전트에 맡겼다.</p>
<p>이를 위해 <code>add-issue</code>라는 전용 스킬을 만들었다. GitHub 이슈만 읽어도 문제를 고칠 수 있을 만큼 상세하게 작성하는 것이 이 스킬의 핵심이었다.</p>
<p><code>add-issue</code>는 같은 내용의 이슈가 이미 있는지 확인하고, <code>main</code> 브랜치와 배포된 버전도 검사한다. 최신 <code>main</code>에서도 아직 고쳐지지 않은 문제일 때만 이슈를 남긴다. 개인정보 등이 이슈에 노출되지 않도록 가리는 기능도 넣었다.</p>
<p>이슈를 남길 때는 Codex 데스크톱 앱의 워크트리 기능을 활용했다. 여러 작업을 서로 다른 워크트리에서 병렬로 진행해 이슈를 동시에 만들었다.</p>
<p>이런 방식으로 작업 속도를 높이는 데에는 많은 토큰이 필요했다. 토큰 사용량은 상당했지만, 토큰 비용은 인건비와 비교하면 매우 저렴하다고 생각한다. 같은 업무를 사람만으로 처리했다면 비용이 30배는 더 들었을 것으로 본다.</p>
<h2>프로덕션 장애로 확인한 차이</h2>
<p>Labor0를 개발하다가 실수로 프로덕션을 중단시킨 적이 있다. 당시에는 어쩔 수 없이 로컬 Codex를 사용해 복구에 필요한 작업을 처리했다.</p>
<p>이 과정에서 Labor0로 일할 때가 로컬 Codex를 직접 사용할 때보다 작업 처리 속도가 훨씬 빠르다는 사실을 확인했다. 이제는 Labor0 없이 Codex만 직접 사용하던 방식으로 돌아가기 어렵다.</p>
<p>그만큼 Labor0의 프로덕션 안정성이 중요해졌다. 지금은 그래프 기반 작업 관리자와 러너처럼 Labor0의 기반을 이루는 구성 요소들의 안정성을 높이는 데 많은 투자를 하고 있다. E2E 테스트를 계속 추가하고 있으며, Codex를 활용해 테스트에 남아 있는 허점도 찾고 있다.</p>
<h2>PR 기반 작업에서 보편적인 작업으로</h2>
<p>현재 Labor0가 관리하는 세션은 PR 기반 작업 흐름에 묶여 있다. <code>CodingTask</code>의 성공 기준도 PR이다.</p>
<p>다만 내부 구조는 이미 <code>CodingTask</code>와 <code>AgentTask</code>로 나뉘어 있다. <code>AgentTask</code>는 아직 내부에만 존재하는 개념으로, 에이전트가 수행할 수 있는 모든 종류의 작업을 나타낸다. PR을 기준으로 하는 <code>CodingTask</code>와 달리 별도의 성공 조건을 가진다.</p>
<p>앞으로는 Labor0가 관리하는 세션을 PR 기반 코딩 작업에서 더 보편적인 작업으로 확장할 계획이다.</p>
<p>러너의 지원 범위도 넓히고 있다. 현재 클라우드에서 호스팅되는 Labor0 러너는 리눅스만 지원한다. 로컬 러너를 이용하면 이미 다른 운영체제의 러너를 사용할 수 있으며, 다른 운영체제를 기반으로 하는 클라우드 러너도 고려하고 있다.</p>
<p>시스템이 관리하는 작업 외에 직접 세션을 확인해야 하는 경우도 있었다. 이런 작업 때문에 Codex 데스크톱을 사용해야 했고, 이를 Labor0 안에서 처리하기 위해 <code>Direct Task</code> 기능을 만들었다.</p>
<p><code>Direct Task</code>는 시스템이 관리하지 않는 세션을 직접 만드는 기능이다. 현재 알파 단계이며 아직 공개하지 않았다.</p>
<p>작업이 빠르게 진행되면서 그동안 무엇이 바뀌었는지 파악하기 위한 기능도 필요해졌다. 그래서 만든 것이 <code>Catch-up</code>이다.</p>
<p><code>Catch-up</code>은 내가 마지막으로 업데이트를 확인한 시점부터 현재까지 무엇이 바뀌었는지를 AI가 요약해 주는 기능이다. 이 기능도 현재 알파 단계다.</p>
<h2>사람이 입력하던 프롬프트까지 자동화하기</h2>
<p>지금은 <code>"labor0" 라벨이 붙은 모든 이슈를 고쳐줘</code> 같은 프롬프트를 내가 반복해서 입력해야 한다.</p>
<p>다음 단계는 이런 프롬프트를 직접 입력하는 일까지 줄이는 것이다. 현재 구상하는 방식은 GitHub 이벤트를 감지하고, 특정 검색 조건을 만족하는 이슈를 Labor0의 작업 그래프에 자동으로 추가하는 것이다.</p>
<p>회사와 프로젝트마다 일하는 방식이 크게 다르기 때문에 특정한 작업 방식에만 맞추지는 않을 생각이다. 각 사용자가 자신의 작업 방식에 맞게 조정할 수 있도록 범용적인 형태로 설계할 계획이다.</p>
<p>이 구조는 GitHub 이슈 외의 신호에도 적용할 수 있다. Grafana나 Sentry 같은 런타임 모니터링 도구에서 문제가 발생하면 작업 그래프에 자동으로 작업을 추가할 수 있다.</p>
<p>슬로 쿼리 로그를 분석해 최적화 작업을 만들거나, 메인 브랜치의 CI가 깨졌을 때 이를 고치는 작업을 생성하는 기능도 추가할 예정이다. 필요한 문서 업데이트 작업도 자동으로 만들 계획이다.</p>
<p>이벤트가 발생했을 때뿐만 아니라 정해진 주기에 맞춰 작업을 반복 실행하는 기능도 있다. 이 기능은 현재 알파 단계다.</p>
<h2>QA도 같은 흐름으로</h2>
<p>회사 블로그에는 <a href="https://theaiplatform.app/blog/automating-pull-request-qa-with-codex-desktop/">Codex Desktop으로 매시간 PR QA를 자동화한 과정</a>을 정리한 글도 썼다.</p>
<p>Codex 데스크톱이 매시간 PR 하나를 골라 빌드하고, 실제 앱을 조작해 변경된 흐름을 검사한 뒤, 스크린샷과 함께 GitHub 리뷰를 남기도록 만든 작업이다. 이것도 Codex 데스크톱으로 직접 구성했다.</p>
<p>앞으로는 이런 QA 작업도 Labor0에서 더 쉽게 만들 수 있도록 지원할 예정이다. 이를 위해 준비하고 있는 기능이 <code>Chatty QA</code>다.</p>
<p><code>Chatty QA</code>는 명백히 잘못된 부분을 발견하면 작업 그래프에 추가해 에이전트가 고치게 한다. 디자인에 관한 결정이 필요하면 스크린샷 등 판단에 필요한 자료와 함께 사용자에게 질문한다. 이 기능도 현재 알파 단계다.</p>
<h2>목표</h2>
<p>우선은 내가 해야 했던 일 가운데 디자인에 관한 결정을 제외한 나머지를 Labor0를 통해 자동화하는 것이 목표다.</p>
<p>더 장기적으로는 지금까지 Codex 데스크톱을 사용해야 했던 작업과 번거롭지만 직접 처리해야 했던 대부분의 작업을 Labor0 안에서 훨씬 편하게 처리할 수 있도록 만들 생각이다.</p>
<p>사람을 작업 과정에서 없애려는 것이 아니다. 에이전트가 처리할 수 있는 일은 계속 진행하게 하고, 사람의 결정이 필요한 순간에만 사람을 부르는 시스템을 만드는 것이 목표다.</p>
]]></content:encoded></item><item><title><![CDATA[작업 기록: 자동 QA 구성]]></title><description><![CDATA[최근에 Zephyr Cloud 의 프로젝트에 자동 QA 시스템을 구성했다. 그 작업 기록이다.

원래 이런 작업은 리눅스에서 하는 게 맞다고 생각했다. 그런데 나는 계속 로컬에서 디버깅해야 했고, 개발 흐름상 맥에서 바로 돌릴 수 있어야 했다.
예전에도 자동 QA를 시도한 적은 있었지만 끝까지 제대로 굴러간 적은 없었다. 그러다가 지난주 중반쯤, “이건 로]]></description><link>https://kdy1.dev/2026-5-14-task-log-auto-qa</link><guid isPermaLink="true">https://kdy1.dev/2026-5-14-task-log-auto-qa</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Wed, 13 May 2026 21:47:17 GMT</pubDate><content:encoded><![CDATA[<p>최근에 Zephyr Cloud 의 프로젝트에 자동 QA 시스템을 구성했다. 그 작업 기록이다.</p>
<hr />
<p>원래 이런 작업은 리눅스에서 하는 게 맞다고 생각했다. 그런데 나는 계속 로컬에서 디버깅해야 했고, 개발 흐름상 맥에서 바로 돌릴 수 있어야 했다.</p>
<p>예전에도 자동 QA를 시도한 적은 있었지만 끝까지 제대로 굴러간 적은 없었다. 그러다가 지난주 중반쯤, “이건 로컬에서부터 디버깅하면 가능하겠다”는 감이 왔다.</p>
<p>실제로 작업을 시작한 건 목요일이었다.</p>
<hr />
<h3>타임라인</h3>
<h4>목요일 — 로컬 자동 QA 재시작</h4>
<p>목요일부터 자동 QA를 다시 구성하기 시작했다.</p>
<p>Codex에게 로컬에서 실행 가능한 스크립트를 짜게 했고, 초기 접근 방식은 Midscene 기반이었다.</p>
<p>앱이 웹 기반이라 자동화 자체는 비교적 쉬운 편이었다.</p>
<p>문제는 Midscene 기반 접근 방식의 특성이었다.</p>
<p>테스트를 실행하면 실제 컴퓨터 화면을 점유했다. 클릭 이벤트를 수행하려면 실제 GUI를 건드려야 했고, 테스트가 도는 동안 컴퓨터를 사실상 사용할 수 없었다.</p>
<p>즉:</p>
<ul>
<li><p>QA는 자동인데</p>
</li>
<li><p>사람은 컴퓨터를 못 쓰는 상태</p>
</li>
</ul>
<p>가 되어버렸다.</p>
<p>이 시점에서 자동 QA의 의미가 꽤 퇴색된다고 느꼈다.</p>
<p>⸻</p>
<h4>금요일 — CDP 기반 백그라운드 자동 QA 성공</h4>
<p>금요일에는 실행 구조를 바꿨다.</p>
<p>핵심은:</p>
<ul>
<li>화면을 점유하지 않고 백그라운드에서 돌리는 것</li>
</ul>
<p>이었다.</p>
<p>여기서 Midscene 기반 접근에서 CDP 기반 구조로 전환했다.</p>
<p>앱이 웹 기반이었기 때문에 CDP를 사용하는 게 잘 맞았고, 브라우저를 직접 제어하면서 백그라운드 실행이 가능해졌다.</p>
<p>다만 단순 브라우저 제어만으로는 부족해서, 판단과 플로우 제어에는 Claude Code를 사용했다.</p>
<p>결과적으로:</p>
<ul>
<li><p>로컬 디버깅 가능</p>
</li>
<li><p>백그라운드 실행 가능</p>
</li>
<li><p>화면 점유 없음</p>
</li>
<li><p>실제 사용자 시나리오 기반 QA 가능</p>
</li>
</ul>
<p>상태까지 도달했다.</p>
<p>그리고 금요일에 처음으로 자동 QA를 끝까지 완주시키는 데 성공했다.</p>
<hr />
<h4>토요일 — CI 연결</h4>
<p>토요일에는 이 QA를 CI에서 실행되도록 연결했다.</p>
<p>아직 레거시 이미지 기반이긴 했지만, 중요한 것은 PR 단위 자동 QA 가 가능해졌다는 점이었다.</p>
<h3>현재 구조</h3>
<p>현재 흐름은 아래처럼 구성되어 있다.</p>
<ol>
<li>PR 작성</li>
</ol>
<p>PR 본문에는 원래 사람이 수동 QA를 할 때 필요한 가이드라인을 적는다.</p>
<p>예를 들면:</p>
<ul>
<li><p>어떤 기능을 확인해야 하는지</p>
</li>
<li><p>어떤 시나리오를 검증해야 하는지</p>
</li>
<li><p>어떤 동작이 기대 결과인지</p>
</li>
</ul>
<p>같은 내용이다.</p>
<p>⸻</p>
<ol>
<li>QA 라벨 부착</li>
</ol>
<p>'needs ai qa' 라벨을 붙이면 깃허브 액션이 이를 감지한다. 그리고 라벨이 붙으면 자동 QA가 시작된다.</p>
<p>⸻</p>
<ol>
<li>CI에서 자동 QA 수행</li>
</ol>
<p>CI 환경에서 자동으로:</p>
<ul>
<li><p>브라우저 실행</p>
</li>
<li><p>시나리오 수행</p>
</li>
<li><p>화면 분석</p>
</li>
<li><p>결과 판별</p>
</li>
</ul>
<p>을 수행한다.</p>
<p>그리고 이 구조의 장점 중 하나는 병렬 처리였다.</p>
<p>PR이 여러 개 열려 있어도 각각 독립적으로 QA를 수행할 수 있다.</p>
<p>사람이 수동 테스트를 하는 구조였다면:</p>
<ul>
<li><p>여러 PR 동시 처리 어려움</p>
</li>
<li><p>컨텍스트 스위칭 발생</p>
</li>
<li><p>반복 작업 증가</p>
</li>
</ul>
<p>문제가 생겼을 텐데, 지금은 그런 부담이 많이 줄었다.</p>
<p>⸻</p>
<ol>
<li>결과 댓글 작성</li>
</ol>
<p>QA가 끝나면 PR에 댓글을 남긴다.</p>
<p>결과는 항목별로:</p>
<ul>
<li><p>PASS</p>
</li>
<li><p>FAIL</p>
</li>
<li><p>INCONCLUSIVE</p>
</li>
</ul>
<p>형태로 정리된다.</p>
<p>그래서 실제로 사람이 확인해야 하는 건:</p>
<ul>
<li><p>실패 항목</p>
</li>
<li><p>애매한 항목</p>
</li>
</ul>
<p>정도로 줄어들었다.</p>
<p>⸻</p>
<p>스크린샷 / 비디오 기록</p>
<p>자동 QA 과정에서:</p>
<ul>
<li><p>스크린샷 저장</p>
</li>
<li><p>비디오 녹화</p>
</li>
</ul>
<p>도 함께 수행한다.</p>
<p>비디오 녹화 기능은 이번 주 수요일(5월 13일)에 추가했다.</p>
<p>기왕 하는 김에 영상도 있으면 좋을 것 같아서 넣었나</p>
]]></content:encoded></item><item><title><![CDATA[성대 행사에 참여했다]]></title><description><![CDATA[어제 성균관대학교에서 열린 간단한 이벤트에 연사로 참여했는데, 질의 응답 시간에 한 얘기들 중 도움이 될만한 것들이 많았다. 귀차니즘을 해결할 수 있다면 글로 정리해서 올릴 것이다.
주제는

개발 지식 / 전공 지식이 필요한가

AI 시대에 필요한 역량

개발 직군의 미래


가 될 것이다.]]></description><link>https://kdy1.dev/2026-5-14-skku-event</link><guid isPermaLink="true">https://kdy1.dev/2026-5-14-skku-event</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Wed, 13 May 2026 21:00:13 GMT</pubDate><content:encoded><![CDATA[<p>어제 성균관대학교에서 열린 간단한 이벤트에 연사로 참여했는데, 질의 응답 시간에 한 얘기들 중 도움이 될만한 것들이 많았다. 귀차니즘을 해결할 수 있다면 글로 정리해서 올릴 것이다.</p>
<p>주제는</p>
<ul>
<li><p>개발 지식 / 전공 지식이 필요한가</p>
</li>
<li><p>AI 시대에 필요한 역량</p>
</li>
<li><p>개발 직군의 미래</p>
</li>
</ul>
<p>가 될 것이다.</p>
]]></content:encoded></item><item><title><![CDATA[한국의 학벌에 대한 생각]]></title><description><![CDATA[내 블로그의 제목이 kdy1: The way I think 인만큼 앞으로는 내 생각을 더 자주 올리려고 한다.
한국 기준으로, 학벌은 사람을 볼 때 꽤나 유용한 지표이지만, 절대적이지는 않다. 경험적인 얘기일 뿐이지만, 성균관대학교 자퇴생으로서 느낀 것들이 몇 가지 있다.
대학까지 간 사람의 학벌은 학습 능력 x 성실함 에 대체로 비례한다. 그래서 의미가 ]]></description><link>https://kdy1.dev/2026-4-4-korean-edu</link><guid isPermaLink="true">https://kdy1.dev/2026-4-4-korean-edu</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Fri, 03 Apr 2026 23:08:31 GMT</pubDate><content:encoded><![CDATA[<p>내 블로그의 제목이 <code>kdy1: The way I think</code> 인만큼 앞으로는 내 생각을 더 자주 올리려고 한다.</p>
<p>한국 기준으로, 학벌은 사람을 볼 때 꽤나 유용한 지표이지만, 절대적이지는 않다. 경험적인 얘기일 뿐이지만, 성균관대학교 자퇴생으로서 느낀 것들이 몇 가지 있다.</p>
<p>대학까지 간 사람의 학벌은 <code>학습 능력 x 성실함</code> 에 <strong>대체로</strong> 비례한다. 그래서 의미가 있는 것 같다. 나도 대학교 덕분에 사람들 큰 걱정 없이 편하게 만날 수 있었다. 근데 <strong>대체로</strong>가 함정인데, 내가 학교를 다니던 시절 기준으론 성대 정도의 학교도 쉽게 들어올 방법이 꽤나 있었고, 그런 경우 저 곱셈식이 어그러져서인지 대체로 문제가 살짝 있었다.</p>
<p>지금 생각해보면 성대의 특성일 수도 있다. 성대생들은 대체로 저 곱셈식에서 성실함의 비중이 꽤 되는 사람들이기 때문이다. 제대로 노력을 해 본 적 없는 사람들은 자기의 주제를 전혀 모르는데, 내 친구들 중엔 그런 사람이 거의 없었다.</p>
]]></content:encoded></item><item><title><![CDATA[Agentic Coding tips 2]]></title><description><![CDATA[This is a translation of https://kdy1.dev/2026-1-31-ai-coding-tips-kr

I recently gave a short presentation about how I use AI. The first part of the slides overlaps with a previous blog post. In this article, I’ll focus on topics that weren’t covere...]]></description><link>https://kdy1.dev/2026-1-31-ai-coding-tips-en</link><guid isPermaLink="true">https://kdy1.dev/2026-1-31-ai-coding-tips-en</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Fri, 30 Jan 2026 23:32:27 GMT</pubDate><content:encoded><![CDATA[<p>This is a translation of <a target="_blank" href="https://kdy1.dev/2026-1-31-ai-coding-tips-kr">https://kdy1.dev/2026-1-31-ai-coding-tips-kr</a></p>
<hr />
<p>I recently gave a short presentation about how I use AI. The first part of the slides overlaps with a <a target="_blank" href="https://kdy1.dev/2026-1-5-ai-coding-agent-tips">previous blog post</a>. In this article, I’ll focus on topics that weren’t covered in that post.</p>
<h2 id="heading-error-messages-and-logging">Error Messages and Logging</h2>
<h2 id="heading-using-concrete-types-and-schemas">Using Concrete Types and Schemas</h2>
<p>The <code>any</code> type is dangerous even for humans—but it’s even more dangerous for AI.</p>
<p>The same applies to things like:</p>
<ul>
<li><p>Unconstrained parsing such as <code>JSON.parse</code></p>
</li>
<li><p>Loose interfaces</p>
</li>
<li><p>Implicit or undocumented data structures</p>
</li>
</ul>
<p>These patterns force AI to make <strong>too many assumptions</strong>.</p>
<p>The real problem is that once an assumption is wrong, <em>all subsequent reasoning can spiral out of control</em>.</p>
<p>So I try to follow these principles as much as possible:</p>
<ul>
<li><p>Use the most concrete types possible instead of <code>any</code></p>
</li>
<li><p>Prefer schema-based parsers over simple parsing<br />  (e.g., Zod or Yup instead of <code>JSON.parse</code>)</p>
</li>
</ul>
<p>With this approach, even if the AI makes assumptions:</p>
<ul>
<li><p>The probability of those assumptions being wrong is lower</p>
</li>
<li><p>When they <em>are</em> wrong, the system fails fast</p>
</li>
</ul>
<p>In other words, this <strong>prevents AI from carrying incorrect reasoning all the way to the end</strong>.</p>
<h2 id="heading-leveraging-github-actions">Leveraging GitHub Actions</h2>
<p>If you look closely, GitHub Actions has some surprisingly strong properties:</p>
<ul>
<li><p>Completely isolated environments</p>
</li>
<li><p>Easy to configure</p>
</li>
<li><p>A large collection of well-prepared examples</p>
</li>
</ul>
<p>Instead of using it only for CI, I started treating it as a <strong>development virtual machine</strong>.</p>
<p>The same idea applies to AI.</p>
<blockquote>
<p>“Whatever a developer can do locally,<br />AI should be able to do in exactly the same way.”</p>
</blockquote>
<h3 id="heading-setting-up-a-development-environment-for-claude-code">Setting Up a Development Environment for Claude Code</h3>
<ul>
<li>Actual code: <a target="_blank" href="https://github.com/delinoio/delidev/blob/abed0d02fd30524bfb2f77f7227bf9560092e949/.github/workflows/claude.yml">https://github.com/delinoio/delidev/blob/abed0d02fd30524bfb2f77f7227bf9560092e949/.github/workflows/claude.yml</a></li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Claude</span> <span class="hljs-string">Code</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">issue_comment:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">created</span>]
  <span class="hljs-attr">pull_request_review_comment:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">created</span>]
  <span class="hljs-attr">issues:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">opened</span>, <span class="hljs-string">assigned</span>]
  <span class="hljs-attr">pull_request_review:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">submitted</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">claude:</span>
    <span class="hljs-attr">if:</span> <span class="hljs-string">|
      (github.event_name == 'issue_comment' &amp;&amp; contains(github.event.comment.body, '@claude')) ||
      (github.event_name == 'pull_request_review_comment' &amp;&amp; contains(github.event.comment.body, '@claude')) ||
      (github.event_name == 'pull_request_review' &amp;&amp; contains(github.event.review.body, '@claude')) ||
      (github.event_name == 'issues' &amp;&amp; (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
</span>    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">issues:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">id-token:</span> <span class="hljs-string">write</span>
      <span class="hljs-attr">actions:</span> <span class="hljs-string">read</span> <span class="hljs-comment"># Required for Claude to read CI results on PRs</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">repository</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">1</span>

      <span class="hljs-comment"># Setup pnpm</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">pnpm</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">pnpm/action-setup@v4.2.0</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Tauri</span> <span class="hljs-string">dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
            sudo apt-get update
            sudo apt-get install -y \
              libwebkit2gtk-4.1-dev \
              libappindicator3-dev \
              librsvg2-dev \
              patchelf
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Rust</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">dtolnay/rust-toolchain@stable</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Claude</span> <span class="hljs-string">Code</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">claude</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">anthropics/claude-code-action@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">claude_code_oauth_token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.CLAUDE_CODE_OAUTH_TOKEN</span> <span class="hljs-string">}}</span>

          <span class="hljs-comment"># This is an optional setting that allows Claude to read CI results on PRs</span>
          <span class="hljs-attr">additional_permissions:</span> <span class="hljs-string">|
            actions: read
</span>
          <span class="hljs-attr">claude_args:</span> <span class="hljs-string">|
            --allowed-tools Bash,WebFetch,WebSearch,Skill
            --model opus</span>
</code></pre>
<p>With GitHub Actions, you can set things up so that:</p>
<ul>
<li><p>Package managers are installed</p>
</li>
<li><p>Build commands are executed (<code>pnpm build</code>, <code>cargo build</code>, etc.)</p>
</li>
<li><p>Tests are run</p>
</li>
</ul>
<p>Once configured this way, Claude Code effectively operates in an environment that’s <strong>almost identical to a local development setup</strong>.</p>
<p>When the environment is this complete, the quality of AI output improves dramatically:</p>
<ul>
<li><p>It stops guessing</p>
</li>
<li><p>It reasons based on actual execution results</p>
</li>
<li><p>It produces “code that actually runs,” not just “code that should work in theory”</p>
</li>
</ul>
<h3 id="heading-managing-clean-commit-messages-with-ai">Managing Clean Commit Messages with AI</h3>
<p><img src="https://cdn.gamma.app/369hvd746fpmyb6/9780314e609f49c496cb045817adfa33/original/seukeurinsyas-2026-01-30-ohu-5.29.37.png" alt="screenshot" /></p>
<h3 id="heading-preventing-pr-description-spam">Preventing PR Description Spam</h3>
<ul>
<li>Actual code: <a target="_blank" href="https://github.com/delinoio/delidev/blob/abed0d02fd30524bfb2f77f7227bf9560092e949/.github/workflows/claude-code-review.yml#L34-L55">https://github.com/delinoio/delidev/blob/abed0d02fd30524bfb2f77f7227bf9560092e949/.github/workflows/claude-code-review.yml#L34-L55</a></li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Claude</span> <span class="hljs-string">Code</span> <span class="hljs-string">Review</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">opened</span>, <span class="hljs-string">synchronize</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">claude-review:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">write</span>
      <span class="hljs-attr">issues:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">id-token:</span> <span class="hljs-string">write</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">repository</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">1</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Dismiss</span> <span class="hljs-string">old</span> <span class="hljs-string">Claude</span> <span class="hljs-string">bot</span> <span class="hljs-string">comments</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">GH_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          REPO="${{ github.repository }}"
          PR_NUMBER="${{ github.event.pull_request.number }}"
</span>
          <span class="hljs-string">gh</span> <span class="hljs-string">api</span> <span class="hljs-string">"repos/$REPO/issues/$PR_NUMBER/comments"</span> <span class="hljs-string">--jq</span> <span class="hljs-string">'.[] | select(.user.login == "claude[bot]") | .node_id'</span> <span class="hljs-string">|</span> <span class="hljs-string">while</span> <span class="hljs-string">read</span> <span class="hljs-string">-r</span> <span class="hljs-string">comment_node_id;</span> <span class="hljs-string">do</span>
            <span class="hljs-string">if</span> [ <span class="hljs-string">-n</span> <span class="hljs-string">"$comment_node_id"</span> ]<span class="hljs-string">;</span> <span class="hljs-string">then</span>
              <span class="hljs-string">gh</span> <span class="hljs-string">api</span> <span class="hljs-string">graphql</span> <span class="hljs-string">-f</span> <span class="hljs-string">query='</span>
                <span class="hljs-string">mutation($id:</span> <span class="hljs-string">ID!)</span> {
                  <span class="hljs-string">minimizeComment(input:</span> {<span class="hljs-attr">subjectId:</span> <span class="hljs-string">$id</span>, <span class="hljs-attr">classifier:</span> <span class="hljs-string">OUTDATED</span>}<span class="hljs-string">)</span> {
                    <span class="hljs-string">minimizedComment</span> {
                      <span class="hljs-string">isMinimized</span>
                    }
                  }
                }<span class="hljs-string">' -f id="$comment_node_id"
            fi
          done

      - name: Run Claude Code Review
        id: claude-review
        uses: anthropics/claude-code-action@v1
        with:
          claude_code_oauth_token: $<span class="hljs-template-variable">{{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}</span>
          allowed_bots: '</span><span class="hljs-string">*'</span>
          <span class="hljs-attr">prompt:</span> <span class="hljs-string">|
            REPO: ${{ github.repository }}
            PR NUMBER: ${{ github.event.pull_request.number }}
</span>
            <span class="hljs-attr">Please review this pull request and provide feedback on:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">Code</span> <span class="hljs-string">quality</span> <span class="hljs-string">and</span> <span class="hljs-string">best</span> <span class="hljs-string">practices</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">Potential</span> <span class="hljs-string">bugs</span> <span class="hljs-string">or</span> <span class="hljs-string">issues</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">Performance</span> <span class="hljs-string">considerations</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">Security</span> <span class="hljs-string">concerns</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">Test</span> <span class="hljs-string">coverage</span>

            <span class="hljs-string">Use</span> <span class="hljs-string">the</span> <span class="hljs-string">repository's</span> <span class="hljs-string">CLAUDE.md</span> <span class="hljs-string">for</span> <span class="hljs-string">guidance</span> <span class="hljs-string">on</span> <span class="hljs-string">style</span> <span class="hljs-string">and</span> <span class="hljs-string">conventions.</span> <span class="hljs-string">Be</span> <span class="hljs-string">constructive</span> <span class="hljs-string">and</span> <span class="hljs-string">helpful</span> <span class="hljs-string">in</span> <span class="hljs-string">your</span> <span class="hljs-string">feedback.</span>

            <span class="hljs-string">Use</span> <span class="hljs-string">`gh</span> <span class="hljs-string">pr</span> <span class="hljs-string">comment`</span> <span class="hljs-string">with</span> <span class="hljs-string">your</span> <span class="hljs-string">Bash</span> <span class="hljs-string">tool</span> <span class="hljs-string">to</span> <span class="hljs-string">leave</span> <span class="hljs-string">your</span> <span class="hljs-string">review</span> <span class="hljs-string">as</span> <span class="hljs-string">a</span> <span class="hljs-string">comment</span> <span class="hljs-string">on</span> <span class="hljs-string">the</span> <span class="hljs-string">PR.</span>

          <span class="hljs-attr">claude_args:</span> <span class="hljs-string">'--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'</span>
</code></pre>
<p>There’s one bug in the Claude Code GitHub review action: <strong>it leaves too many review comments</strong>.</p>
<p>This can easily result in PRs being flooded with AI-generated comments.</p>
<h2 id="heading-how-to-use-ai-reviews-effectively">How to Use AI Reviews Effectively</h2>
<h3 id="heading-core-assumptions">Core Assumptions</h3>
<p>Let’s be explicit about the premises:</p>
<ul>
<li><p>Human reviews are slow</p>
</li>
<li><p>Human reviews are expensive</p>
</li>
</ul>
<p>Therefore, the goal is to <strong>minimize human involvement</strong>.</p>
<p>The strategy I chose is:</p>
<ol>
<li><p>Run AI reviews and CI first</p>
</li>
<li><p>Humans do not intervene until AI gives an OK</p>
</li>
<li><p>Only when tests and automated reviews pass</p>
</li>
<li><p>A human performs the final review</p>
</li>
</ol>
<p>In short:</p>
<blockquote>
<p>Humans act only as the “final approver.”</p>
</blockquote>
<h3 id="heading-1-using-a-github-app">1. Using a GitHub App</h3>
<p>By integrating AI reviews as a GitHub App, reviews start automatically as soon as a PR is opened.</p>
<p>At this stage, AI filters out:</p>
<ul>
<li><p>Code style issues</p>
</li>
<li><p>Obvious bugs</p>
</li>
<li><p>Structural problems</p>
</li>
</ul>
<h3 id="heading-2-applying-changes-via-github-actions">2. Applying Changes via GitHub Actions</h3>
<p><img src="https://cdn.gamma.app/369hvd746fpmyb6/9151ee873c544e18a87b6143de64214c/original/image.png" alt="screenshot" /></p>
<p>Using tools like the Claude Code GitHub Action makes parallel processing much easier. Checking out code locally should be reserved for situations where human, local testing is truly required.</p>
<h3 id="heading-3-using-ci-as-a-gatekeeper">3. Using CI as a Gatekeeper</h3>
<p><img src="https://cdn.gamma.app/369hvd746fpmyb6/5afe35a1a3714b81976456d41f3bbdcb/original/seukeurinsyas-2026-01-30-ohu-5.33.30.png" alt="screenshot" /></p>
<p>The key is to treat CI not just as a testing tool, but as:</p>
<blockquote>
<p><strong>A barrier between AI and humans</strong></p>
</blockquote>
<p>If AI-generated changes can’t pass CI, they never reach human reviewers. That alone significantly reduces review costs.</p>
<hr />
<h2 id="heading-qampa">Q&amp;A</h2>
<h3 id="heading-why-mcp-is-unnecessary-for-this-use-case">Why MCP Is Unnecessary for This Use Case</h3>
<p>Imagine there is a CLI that provides the exact same capabilities as a specific MCP server. Anything you can do through MCP could also be done through that CLI. This is why Vercel chose to improve its CLI instead of building an MCP server.</p>
]]></content:encoded></item><item><title><![CDATA[Ai 코딩 팁 2 (한국어)]]></title><description><![CDATA[발표 자료: https://gamma.app/docs/AI--2a52e7tk3eb1ch1

AI 활용법 관련해서 간단하게 발표를 했다. 발표 자료 앞쪽은 전에 블로그에 올린 글이랑 같은 내용이다. 이 글에서는 기존 글에서 다루지 않은 내용들을 다루겠다.
에러 메시지 및 로깅
구체적 타입 및 스키마 활용
any 타입은 사람에게도 위험하지만, AI에게는 더 위험하다.
마찬가지로,

JSON.parse처럼 아무 제약 없는 파싱

느슨한 인터페이스

...]]></description><link>https://kdy1.dev/2026-1-31-ai-coding-tips-kr</link><guid isPermaLink="true">https://kdy1.dev/2026-1-31-ai-coding-tips-kr</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Fri, 30 Jan 2026 23:31:14 GMT</pubDate><content:encoded><![CDATA[<ul>
<li>발표 자료: <a target="_blank" href="https://gamma.app/docs/AI--2a52e7tk3eb1ch1">https://gamma.app/docs/AI--2a52e7tk3eb1ch1</a></li>
</ul>
<p>AI 활용법 관련해서 간단하게 발표를 했다. 발표 자료 앞쪽은 <a target="_blank" href="https://kdy1.dev/2026-1-5-ai-coding-agent-tips">전에 블로그에 올린 글</a>이랑 같은 내용이다. 이 글에서는 기존 글에서 다루지 않은 내용들을 다루겠다.</p>
<h2 id="heading-7jeq65siouploylnoyngcdrsi8g66gc6rmf">에러 메시지 및 로깅</h2>
<h2 id="heading-6rws7lk07kcbio2dgoyehsdrsi8g7iqk7ykk66eiio2znoyaqq">구체적 타입 및 스키마 활용</h2>
<p><code>any</code> 타입은 사람에게도 위험하지만, AI에게는 더 위험하다.</p>
<p>마찬가지로,</p>
<ul>
<li><p><code>JSON.parse</code>처럼 아무 제약 없는 파싱</p>
</li>
<li><p>느슨한 인터페이스</p>
</li>
<li><p>암묵적인 데이터 구조</p>
</li>
</ul>
<p>이런 것들은 AI가 <strong>너무 많은 가정을 하게 만든다</strong>.</p>
<p>문제는 그 가정이 틀리면, 이후의 모든 판단이 연쇄적으로 꼬인다는 점이다.</p>
<p>그래서 가능한 한 다음을 지향한다.</p>
<ul>
<li><p><code>any</code> 타입 대신 최대한 구체적인 타입 사용</p>
</li>
<li><p>단순 파싱 대신 스키마 기반 파서 사용<br />  (예: <code>JSON.parse</code> 대신 Zod, Yup 같은 라이브러리)</p>
</li>
</ul>
<p>이렇게 하면 AI가 가정을 하더라도,</p>
<ul>
<li><p>그 가정이 틀릴 확률이 줄어들고</p>
</li>
<li><p>틀렸을 경우 즉시 실패하도록 만들 수 있다</p>
</li>
</ul>
<p>즉, <strong>AI가 잘못된 추론을 끝까지 끌고 가는 상황을 예방</strong>할 수 있다.</p>
<h2 id="heading-github-actions">GitHub Actions 활용</h2>
<p>GitHub Actions를 잘 보면 꽤 괜찮은 특징을 갖고 있다.</p>
<ul>
<li><p>완전히 격리된 환경</p>
</li>
<li><p>구성하기 쉬움</p>
</li>
<li><p>이미 잘 구성된 예제가 많음</p>
</li>
</ul>
<p>이걸 단순히 CI 용도로만 쓰지 않고,<br /><strong>개발용 가상 머신</strong>처럼 사용하는 접근을 했다.</p>
<p>AI에게도 마찬가지다.</p>
<blockquote>
<p>“로컬에서 개발자가 할 수 있는 걸<br />AI도 똑같이 할 수 있게 만들어준다”</p>
</blockquote>
<p>라는 관점이다.</p>
<h3 id="heading-claude-code">Claude Code를 위한 개발 환경 구성</h3>
<ul>
<li>실제 코드: <a target="_blank" href="https://github.com/delinoio/delidev/blob/abed0d02fd30524bfb2f77f7227bf9560092e949/.github/workflows/claude.yml">https://github.com/delinoio/delidev/blob/abed0d02fd30524bfb2f77f7227bf9560092e949/.github/workflows/claude.yml</a></li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Claude</span> <span class="hljs-string">Code</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">issue_comment:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">created</span>]
  <span class="hljs-attr">pull_request_review_comment:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">created</span>]
  <span class="hljs-attr">issues:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">opened</span>, <span class="hljs-string">assigned</span>]
  <span class="hljs-attr">pull_request_review:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">submitted</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">claude:</span>
    <span class="hljs-attr">if:</span> <span class="hljs-string">|
      (github.event_name == 'issue_comment' &amp;&amp; contains(github.event.comment.body, '@claude')) ||
      (github.event_name == 'pull_request_review_comment' &amp;&amp; contains(github.event.comment.body, '@claude')) ||
      (github.event_name == 'pull_request_review' &amp;&amp; contains(github.event.review.body, '@claude')) ||
      (github.event_name == 'issues' &amp;&amp; (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
</span>    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">issues:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">id-token:</span> <span class="hljs-string">write</span>
      <span class="hljs-attr">actions:</span> <span class="hljs-string">read</span> <span class="hljs-comment"># Required for Claude to read CI results on PRs</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">repository</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">1</span>

      <span class="hljs-comment"># Setup pnpm </span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">pnpm</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">pnpm/action-setup@v4.2.0</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Tauri</span> <span class="hljs-string">dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
            sudo apt-get update
            sudo apt-get install -y \
              libwebkit2gtk-4.1-dev \
              libappindicator3-dev \
              librsvg2-dev \
              patchelf
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Rust</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">dtolnay/rust-toolchain@stable</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Claude</span> <span class="hljs-string">Code</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">claude</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">anthropics/claude-code-action@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">claude_code_oauth_token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.CLAUDE_CODE_OAUTH_TOKEN</span> <span class="hljs-string">}}</span>

          <span class="hljs-comment"># This is an optional setting that allows Claude to read CI results on PRs</span>
          <span class="hljs-attr">additional_permissions:</span> <span class="hljs-string">|
            actions: read
</span>
          <span class="hljs-attr">claude_args:</span> <span class="hljs-string">|
            --allowed-tools Bash,WebFetch,WebSearch,Skill
            --model opus</span>
</code></pre>
<p>GitHub Actions에서:</p>
<ul>
<li><p>패키지 매니저 설치</p>
</li>
<li><p>빌드 명령 실행 (<code>pnpm build</code>, <code>cargo build</code> 등)</p>
</li>
<li><p>테스트 실행</p>
</li>
</ul>
<p>까지 모두 가능하게 구성해두면,<br />Claude Code는 사실상 <strong>로컬 개발 환경과 거의 동일한 조건</strong>에서 동작한다.</p>
<p>이렇게 환경이 갖춰져 있으면 AI의 작업 품질이 눈에 띄게 좋아진다.</p>
<ul>
<li><p>추측으로 답하지 않고</p>
</li>
<li><p>실제 실행 결과를 기반으로 판단하고</p>
</li>
<li><p>“이론상 맞는 코드” 대신 “실제로 도는 코드”를 만든다</p>
</li>
</ul>
<h3 id="heading-ai">AI로 커밋 메시지 예쁘게 관리하기</h3>
<p><img src="https://cdn.gamma.app/369hvd746fpmyb6/9780314e609f49c496cb045817adfa33/original/seukeurinsyas-2026-01-30-ohu-5.29.37.png" alt /></p>
<h3 id="heading-pr">PR 본문 스팸 방지</h3>
<ul>
<li>실제 코드: <a target="_blank" href="https://github.com/delinoio/delidev/blob/abed0d02fd30524bfb2f77f7227bf9560092e949/.github/workflows/claude-code-review.yml#L34-L55">https://github.com/delinoio/delidev/blob/abed0d02fd30524bfb2f77f7227bf9560092e949/.github/workflows/claude-code-review.yml#L34-L55</a></li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Claude</span> <span class="hljs-string">Code</span> <span class="hljs-string">Review</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">opened</span>, <span class="hljs-string">synchronize</span>]
    <span class="hljs-comment"># Optional: Only run on specific file changes</span>
    <span class="hljs-comment"># paths:</span>
    <span class="hljs-comment">#   - "src/**/*.ts"</span>
    <span class="hljs-comment">#   - "src/**/*.tsx"</span>
    <span class="hljs-comment">#   - "src/**/*.js"</span>
    <span class="hljs-comment">#   - "src/**/*.jsx"</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">claude-review:</span>
    <span class="hljs-comment"># Optional: Filter by PR author</span>
    <span class="hljs-comment"># if: |</span>
    <span class="hljs-comment">#   github.event.pull_request.user.login == 'external-contributor' ||</span>
    <span class="hljs-comment">#   github.event.pull_request.user.login == 'new-developer' ||</span>
    <span class="hljs-comment">#   github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'</span>

    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">write</span>
      <span class="hljs-attr">issues:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">id-token:</span> <span class="hljs-string">write</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">repository</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">1</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Dismiss</span> <span class="hljs-string">old</span> <span class="hljs-string">Claude</span> <span class="hljs-string">bot</span> <span class="hljs-string">comments</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">GH_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          # Get PR comments from claude[bot] and hide them as outdated
          REPO="${{ github.repository }}"
          PR_NUMBER="${{ github.event.pull_request.number }}"
</span>
          <span class="hljs-comment"># Get issue comments (gh pr comment creates issue comments, not review comments)</span>
          <span class="hljs-string">gh</span> <span class="hljs-string">api</span> <span class="hljs-string">"repos/$REPO/issues/$PR_NUMBER/comments"</span> <span class="hljs-string">--jq</span> <span class="hljs-string">'.[] | select(.user.login == "claude[bot]") | .node_id'</span> <span class="hljs-string">|</span> <span class="hljs-string">while</span> <span class="hljs-string">read</span> <span class="hljs-string">-r</span> <span class="hljs-string">comment_node_id;</span> <span class="hljs-string">do</span>
            <span class="hljs-string">if</span> [ <span class="hljs-string">-n</span> <span class="hljs-string">"$comment_node_id"</span> ]<span class="hljs-string">;</span> <span class="hljs-string">then</span>
              <span class="hljs-string">echo</span> <span class="hljs-string">"Hiding review comment: $comment_node_id"</span>
              <span class="hljs-string">gh</span> <span class="hljs-string">api</span> <span class="hljs-string">graphql</span> <span class="hljs-string">-f</span> <span class="hljs-string">query='</span>
                <span class="hljs-string">mutation($id:</span> <span class="hljs-string">ID!)</span> {
                  <span class="hljs-string">minimizeComment(input:</span> {<span class="hljs-attr">subjectId:</span> <span class="hljs-string">$id</span>, <span class="hljs-attr">classifier:</span> <span class="hljs-string">OUTDATED</span>}<span class="hljs-string">)</span> {
                    <span class="hljs-string">minimizedComment</span> {
                      <span class="hljs-string">isMinimized</span>
                    }
                  }
                }<span class="hljs-string">' -f id="$comment_node_id"
            fi
          done

      - name: Run Claude Code Review
        id: claude-review
        uses: anthropics/claude-code-action@v1
        with:
          claude_code_oauth_token: $<span class="hljs-template-variable">{{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}</span>
          allowed_bots: '</span><span class="hljs-string">*'</span>
          <span class="hljs-attr">prompt:</span> <span class="hljs-string">|
            REPO: ${{ github.repository }}
            PR NUMBER: ${{ github.event.pull_request.number }}
</span>
            <span class="hljs-attr">Please review this pull request and provide feedback on:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">Code</span> <span class="hljs-string">quality</span> <span class="hljs-string">and</span> <span class="hljs-string">best</span> <span class="hljs-string">practices</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">Potential</span> <span class="hljs-string">bugs</span> <span class="hljs-string">or</span> <span class="hljs-string">issues</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">Performance</span> <span class="hljs-string">considerations</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">Security</span> <span class="hljs-string">concerns</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">Test</span> <span class="hljs-string">coverage</span>

            <span class="hljs-string">Use</span> <span class="hljs-string">the</span> <span class="hljs-string">repository's</span> <span class="hljs-string">CLAUDE.md</span> <span class="hljs-string">for</span> <span class="hljs-string">guidance</span> <span class="hljs-string">on</span> <span class="hljs-string">style</span> <span class="hljs-string">and</span> <span class="hljs-string">conventions.</span> <span class="hljs-string">Be</span> <span class="hljs-string">constructive</span> <span class="hljs-string">and</span> <span class="hljs-string">helpful</span> <span class="hljs-string">in</span> <span class="hljs-string">your</span> <span class="hljs-string">feedback.</span>

            <span class="hljs-string">Use</span> <span class="hljs-string">`gh</span> <span class="hljs-string">pr</span> <span class="hljs-string">comment`</span> <span class="hljs-string">with</span> <span class="hljs-string">your</span> <span class="hljs-string">Bash</span> <span class="hljs-string">tool</span> <span class="hljs-string">to</span> <span class="hljs-string">leave</span> <span class="hljs-string">your</span> <span class="hljs-string">review</span> <span class="hljs-string">as</span> <span class="hljs-string">a</span> <span class="hljs-string">comment</span> <span class="hljs-string">on</span> <span class="hljs-string">the</span> <span class="hljs-string">PR.</span>

          <span class="hljs-comment"># See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md</span>
          <span class="hljs-comment"># or https://code.claude.com/docs/en/cli-reference for available options</span>
          <span class="hljs-attr">claude_args:</span> <span class="hljs-string">'--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'</span>
</code></pre>
<p>Claude Code GitHub 리뷰 액션에는 버그가 하나 있다. 리뷰 코멘트를 <strong>너무 많이 남긴다</strong>.</p>
<p>이 때문에 PR이 리뷰 댓글로 도배되는 문제가 생긴다.</p>
<h2 id="heading-ai-1">AI 리뷰 활용법</h2>
<h3 id="heading-6riw67o4ioygkeq3va">기본 접근</h3>
<p>전제부터 분명히 하자.</p>
<ul>
<li><p>인간 리뷰는 느리다</p>
</li>
<li><p>인간 리뷰는 비싸다</p>
</li>
</ul>
<p>그래서 <strong>인간 리뷰를 최소화해야 한다</strong>.</p>
<p>이를 위해 선택한 전략은 다음과 같다.</p>
<ol>
<li><p>AI 리뷰 + CI를 먼저 통과시킨다</p>
</li>
<li><p>AI가 OK를 내릴 때까지는 인간이 개입하지 않는다</p>
</li>
<li><p>테스트와 자동 리뷰가 모두 통과된 경우에만</p>
</li>
<li><p>사람이 최종 리뷰를 한다</p>
</li>
</ol>
<p>즉,</p>
<blockquote>
<p>인간은 “최종 승인자” 역할만 맡는다</p>
</blockquote>
<ol>
<li><h3 id="heading-github">GitHub 앱 활용</h3>
</li>
</ol>
<p>AI 리뷰를 GitHub 앱 형태로 붙이면, PR 생성과 동시에 자동 리뷰가 시작된다.</p>
<p>이 단계에서는:</p>
<ul>
<li><p>코드 스타일</p>
</li>
<li><p>명백한 버그</p>
</li>
<li><p>구조적인 문제</p>
</li>
</ul>
<p>같은 것들을 AI가 먼저 걸러낸다.</p>
<ol start="2">
<li><h3 id="heading-github-actions-1">GitHub Actions를 사용해서 반영</h3>
<p> <img src="https://cdn.gamma.app/369hvd746fpmyb6/9151ee873c544e18a87b6143de64214c/original/image.png" alt /></p>
<p> 스크린샷과 같이 Claude Code GitHub Action등을 사용해서 반영해야 병렬 처리가 쉽다. 로컬에 체크아웃하는 것은 사람의 테스팅처럼 로컬에서 해야만 하는 상황에만 하는 것을 추천한다.</p>
</li>
<li><h3 id="heading-ci">CI 활용</h3>
</li>
</ol>
<p><img src="https://cdn.gamma.app/369hvd746fpmyb6/5afe35a1a3714b81976456d41f3bbdcb/original/seukeurinsyas-2026-01-30-ohu-5.33.30.png" alt /></p>
<p>중요한 건 CI를 단순한 테스트 도구가 아니라,</p>
<blockquote>
<p><strong>AI와 인간 사이의 방파제</strong></p>
</blockquote>
<p>로 사용하는 것이다. AI가 통과시키지 못한 변경사항은, 인간에게 도달하지 않게 만드는 것만으로도 리뷰 비용은 크게 줄어든다.</p>
<hr />
<h2 id="heading-qampa">Q&amp;A</h2>
<h3 id="heading-mcp">MCP가 필요없는 스펙인 이유</h3>
<p>특정 MCP 서버랑 똑같은 기능을 가진 CLI가 있다고 생각해보자. MCP를 통해서 수행할 수 있는 모든 건 CLI를 통해서도 수행할 수 있을 것이다. Vercel이 MCP를 만들지 않고 CLI 명령어를 개선한 것은 그래서이다.</p>
]]></content:encoded></item><item><title><![CDATA[Tips for AI coding agents]]></title><description><![CDATA[I’ve been using AI coding agents for a while, and I’d like to share my findings. The findings are mainly about Claude Code, but may apply to all coding agents, like opencode, if there’s no note about it.  

Intuitive API is a must.
Everything should ...]]></description><link>https://kdy1.dev/2026-1-5-ai-coding-agent-tips</link><guid isPermaLink="true">https://kdy1.dev/2026-1-5-ai-coding-agent-tips</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Mon, 05 Jan 2026 06:28:02 GMT</pubDate><content:encoded><![CDATA[<p>I’ve been using AI coding agents for a while, and I’d like to share my findings. The findings are mainly about Claude Code, but may apply to all coding agents, like opencode, if there’s no note about it.  </p>
<hr />
<h3 id="heading-intuitive-api-is-a-must">Intuitive API is a must.</h3>
<p>Everything should work intuitively, based on the <strong>name</strong>. AI tends to avoid digging into the lower layer unless you explicitly instruct it to fix it. For example, if <code>enabled: false</code> does not work in the code below, AI will fail to fix it most of the time because it <em>assumes</em> that the option works as expected.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767580108213/41e090a1-8546-4311-abec-666c94f6c4e3.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-subagents-may-reduce-context-window-usage">Subagents may reduce context window usage.</h3>
<blockquote>
<p>Use subagents proactively.</p>
</blockquote>
<p>You can utilize subagents to reduce the usage of the <strong>main</strong> context window. The only thing you need to do is saying <code>Use subagents proactively</code> in the prompt. It’s useful when you need to do a very large task. It makes Claude Code less intelligent for tasks that require the organic analysis of multiple clues, though.</p>
<p>Note: I didn’t define any subagents in my repository. General subagents are enough for this trick.</p>
<h3 id="heading-the-api-reference-document-reduces-the-guess-and-grep-loop">The API reference document reduces the guess-and-grep loop.</h3>
<p>If you don’t specify the name of the method you want to fix, the AI agent will guess the method/variable/module name and grep for it <strong>repeatedly</strong> until it finds the relevant code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767582431474/95ec0a3e-f479-486b-bbbf-605cf491f911.png" alt class="image--center mx-auto" /></p>
<p>This means that if you can provide an API reference document listing all method names, the coding agent will be much more efficient. As a side note, the best thing is an RPC API specification file that lists all method names and only the RPC message name.</p>
<pre><code class="lang-plaintext">syntax = "proto3";
package delino.appcore.v1;
option go_package = "github.com/delinoio/cloud/rpc/appcore";

import "appcore/auth.proto";

service AppCore {
  rpc UpdateUserProfile(UpdateUserProfileRequest) returns (UpdateUserProfileResponse);
}
</code></pre>
<p>Mentioning this file would give the AI agent</p>
<ul>
<li><p>the name of RPC methods to grep for</p>
</li>
<li><p>the names of RPC message types to grep for</p>
</li>
</ul>
<p>so the AI agent will never need to repeatedly guess the names.</p>
<h3 id="heading-provide-the-design-document-and-ask-the-ai-agent-to-update-it">Provide the design document, and ask the AI agent to update it.</h3>
<p>The source of truth <strong>was</strong> the code. And yeah, the primary source of truth is still code. But you can’t feed the whole code into AI agents, and updating code requires lots of tokens. I’m not talking about the token cost. It’s about the context window size. That’s why we need a specification document that is <strong>up-to-date with the codebase</strong> and <strong>committed to the repository</strong>.</p>
<p>Luckily, having one is easy. You don’t need to update it manually. Instead, instruct the AI agent to read the design document before any task, and update it as required.</p>
<h3 id="heading-you-may-start-building-in-a-separate-session-after-creating-a-plan">You may start building in a separate session after creating a plan.</h3>
<ul>
<li>Applies to: Claude Code.</li>
</ul>
<p>The plan mode is great. It’s a really wonderful feature that allows you to create a comprehensive task input prompt that mentions all the relevant files. But sometimes, Claude Code runs compaction nearly immediately after approving the plan because it does not clear the context before executing the plan. If you want, you can instruct it to write the comprehensive task prompt as a Markdown file, and <code>/clear</code> the context window, and paste it there.</p>
<p>In this way, Claude Code will run with a fresh context window, reading referenced files as required. It’s useful if you've done a lot of ping-pong with the planning agent.</p>
<h3 id="heading-provide-project-structure">Provide project structure.</h3>
<p>You can provide the project structure using the instruction file (<code>CLAUDE.md</code>/<code>AGENTS.md</code>) or a custom command. Giving the project structure also reduces the number of tokens wasted to guess the project structure.</p>
<p>Personally, I prefer a monorepo, so I have commands like <code>/work-on-devbird</code> that mentions the relevant frontend/backend directory, the RPC definition, and the design documents.</p>
<h3 id="heading-mention-the-filepath-or-the-names-of-utility-functions">Mention the filepath or the names of utility functions.</h3>
<p>This is a good fit for the instruction files, like <code>CLAUDE.md</code> and <code>AGENTS.md</code>. The AI agent has limited knowledge of the project, so it may repeat the same code over and over. It isn’t good. If you instruct it to read the utility file beforehand, it will reuse the existing function.</p>
<h3 id="heading-if-you-dont-understand-something-throw-the-whole-text-to-the-plan-mode">If you don’t understand something, throw the whole text to the Plan Mode.</h3>
<p>You don’t need to understand everything, seriously. You don’t need to give it a perfect prompt. Instead, you can use plan mode to expand your thoughts within the project context. Sometimes I didn’t understand some requirements because they are specific to the project, but Claude Code's plan mode expanded them into a concrete plan prompt. You can even ask Claude Code to ask you back. You can do clarification based on the questions the Claude Code asks.</p>
]]></content:encoded></item><item><title><![CDATA[2025년 회고]]></title><description><![CDATA[들어가며
2025년은 나한테 롤러코스터 같은 한 해였다. 많은 일들이 있었고 환경의 변화도 많았다. 미국과 한국을 오갔고, 소속된 곳이 바뀌었으며, 새로운 사람들을 만났다. 지난 1년을 타임라인을 따라 정리해 본다.
커리어 변화
4월 16일: Vercel 본사 근무를 위한 Relocation
Vercel 본사 근무를 위해 미국으로 이동했다. 짧지 않은 기간 동안 현지에서 일하며, 이전과는 다른 환경과 문화 속에서 일을 하게 됐다.
6월 16일:...]]></description><link>https://kdy1.dev/2025</link><guid isPermaLink="true">https://kdy1.dev/2025</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Sat, 20 Dec 2025 02:15:36 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-65ok7ja06rca66mw">들어가며</h2>
<p>2025년은 나한테 롤러코스터 같은 한 해였다. 많은 일들이 있었고 환경의 변화도 많았다. 미국과 한국을 오갔고, 소속된 곳이 바뀌었으며, 새로운 사람들을 만났다. 지난 1년을 타임라인을 따라 정리해 본다.</p>
<h2 id="heading-7luk66as7ja0iouzgo2zla">커리어 변화</h2>
<h3 id="heading-4-16-vercel-relocation">4월 16일: Vercel 본사 근무를 위한 Relocation</h3>
<p>Vercel 본사 근무를 위해 미국으로 이동했다. 짧지 않은 기간 동안 현지에서 일하며, 이전과는 다른 환경과 문화 속에서 일을 하게 됐다.</p>
<h3 id="heading-6-16">6월 16일: 귀국</h3>
<p>두 달 정도의 미국 생활을 마치고 한국으로 돌아왔다. 이 시점까지도 이후의 방향이 명확하게 정해져 있던 것은 아니었다.</p>
<h3 id="heading-7-vercel">7월: Vercel 퇴사</h3>
<p>어쩌다 보니 Vercel을 퇴사하게 됐다. 당시에는 회사가 지나치게 무례하다고 느꼈고, 더 이상 같이 일하고 싶지 않다는 감정이 컸다. 분노와 피로가 동시에 있었다. 말해봤자 무슨 소용이 있을까 싶을 정도였다.</p>
<p>시간이 조금 지난 뒤, 미국 기업 문화에 대해 미국에 거주하는 분들의 이야기를 듣고, 또 인공지능을 통해 정리해보면서 그 상황을 다시 바라보게 됐다. 미국 정서 기준으로 보면 회사 쪽이 특별히 잘못했다고 보기는 어려웠다. 한국 기준으로는 쉽게 받아들이기 힘든 방식이었지만, 억울함을 말하고 싶은 건 아니다. 그 당시의 나는 그렇게 느꼈고, 그게 전부였다.</p>
<h3 id="heading-7-delino">7월: Delino 창업</h3>
<p>나는 원래 창업이 하고 싶었다. 아이템도 여러 개 생각해두고 있었다. 마침 퇴사를 하게 되었고, 결과적으로는 잘 된 선택이었다고 생각한다. 감정과는 별개로, 방향을 바꾸기에 적절한 시점이었다.</p>
<h3 id="heading-8-9-real-prompter">8월 9일: Real Prompter 런칭</h3>
<p>Claude Code를 활용해 여러 서비스를 만들면서 한 가지를 계속 느꼈다. LLM을 제대로 활용하는 것은 생각보다 어렵다는 점이었다. 많은 사람들이 “프롬프팅은 어떻게 하느냐”고 물어봤고, 나는 반복해서 비슷한 답을 해왔다.</p>
<p>Roo Cline 등을 사용해 내가 실제로 하던 작업 방식들을 블로그와 SNS를 통해 여러 번 공유했고, 이를 반복하다 보니 서비스화하는 편이 낫겠다는 생각이 들었다. 그렇게 Real Prompter를 만들게 됐다.</p>
<h3 id="heading-10-14-autodev">10월 14일: AutoDev 런칭</h3>
<p>LLM을 잘 사용하는 것이 요령이나 감각의 문제인 것은 맞지만, 시스템적인 방식으로 근본적으로 해결할 수 있는 문제라고 생각했다. AI 덕분에 그 요령이나 감각조차 자동화가 가능한 시대라는 것이다.</p>
<p>AutoDev는 내가 이전부터 계속 이야기해오던 작업 방식을 자동화한 도구였다. 내가 강조해온 핵심은 몇 개 더 있지만, 가장 핵심적인 것은 세 가지였다.</p>
<ol>
<li><p>복잡한 작업을 적절한 단위로 쪼개는 것</p>
</li>
<li><p>여러 세션에 걸쳐 반복적으로 검증하고 피드백을 받는 구조</p>
</li>
<li><p>CI와 자동 테스트를 활용한 회귀(regression) 방지</p>
</li>
</ol>
<p>이건 새로운 아이디어라기보다는, 내가 계속 해오던 방식이었다. AutoDev는 그 방식을 도구로 옮긴 결과였다.</p>
<h3 id="heading-10-15">10월 15일: 인프런 멘토링</h3>
<p>돈을 받고 진행한 내 인생 첫번째 멘토링이라서 적기로 했다. 인프런을 통해서 진행한 것이었고, AI 활용법 및 오픈소스 관련 전략에 대해서 많은 얘기를 나누었다. 가격이 한국 기준으론 상당한 가격이었기에 감사한 마음으로 사전 질문들 및 추후 추가 질문들도 답변해드렸다.</p>
<h3 id="heading-10-23-autodev-devbird">10월 23일: AutoDev → DevBird 리브랜딩</h3>
<p>Microsoft에서 만든 AutoDev와 이름이 겹치면서 SEO가 사실상 불가능했다. 결국 DevBird로 리브랜딩했다. 제품의 방향이나 철학이 바뀐 것은 아니었다.</p>
<h3 id="heading-10-27-zack">10월 27일: Zack과 미팅</h3>
<p>Zephyr Cloud의 CEO인 Zack과 미팅을 가졌다. 네트워킹으로 분류할 수도 있었지만, 이후의 커리어와 연결되는 지점이 있어서 이 글에서는 커리어 이벤트로 남긴다.</p>
<h3 id="heading-11-1-zephyr-cloud">11월 1일: Zephyr Cloud 업무 시작</h3>
<hr />
<h2 id="heading-66eo7j2aioyxro2wiq">많은 여행</h2>
<p>이번 해엔 나답지 않게 여행을 많이 다녔다.</p>
<h3 id="heading-4-16-6-16-vercel">4월 16일 ~ 6월 16일: 미국 Vercel 근무</h3>
<p>미국에서 Vercel과 함께 일한 이 두 달은 즐거움과 떨림이 공존했던 시간이었다.<br />낯선 환경에서 일한다는 점에서 긴장도 됐지만, 좋은 친구들을 만나 덕분에 생각보다 빠르게 적응할 수 있었다. 새로운 사람들과 함께 일하며 많이 배웠다.</p>
<h3 id="heading-10">10월 초: 속초 가족 여행</h3>
<p>힐링의 시간이었다. 맛있는 음식을 함께 먹는 시간이 특히 좋았지만, 집에 두고 온 고양이가 계속 마음에 걸려서 조금 일찍 돌아와야 했던 점은 아쉬움으로 남았다.</p>
<h3 id="heading-10-1">10월 중순: 부산 여행</h3>
<p>동생이랑 부산 여행을 갔다왔다. 열심히 걸었지만 맛있는 음식을 너무 많이 먹었는지 갔다와서 재보니까 체중이 조금 늘었다. 그리고 갈 때 ITX를 탔다. 깨끗하긴한데 너무 느렸다. 그래서 올 때는 KTX 탔다.</p>
<p>해동 용궁사 같은 곳도 보고 왔고, 이재모 피자 같은 맛집도 많이 찾아다녔다.</p>
<h3 id="heading-11-16-23-for-zephyr-cloud">11월 16일 ~ 23일: 미국 출장 (for Zephyr Cloud)</h3>
<p>너무 재밌었다. 일주일간 한 customer onsite였는데 업무 시간엔 일을 엄청나게 많이 했고, 어쩌다보니 앞에 나가서 데모 발표도 했다. 그리고 업무 외 시간에는 대표분이 렌트하신 차로 같이 놀러다녔는데 이게 정말 재밌었다. 재밌는 여행을 할 수 있게 해주신 대표분께 감사드린다.</p>
<h3 id="heading-11-12">11월 말 ~ 12월 초: 일본 도쿄 여행</h3>
<p>동생이랑 일본 도쿄로 짧은 여행을 갔다왔다. 도쿄 디즈니랜드 가서 재밌게 놀았고, 미녀와 야수라는 가장 인기 많은 놀이기구에 충격을 받았다. 그래도 디즈니랜드에서 다른 건 다 재밌었어서 만족스러웠다.</p>
<p>근데 숙소 위치를 조금 잘못 잡았던 것 같다. 신주쿠 가부키초 쪽이었는데 저녁엔 무서워서 안 나갔다.</p>
<h3 id="heading-12">12월 중순: 속초 가족 여행</h3>
<p>홈캠 등의 장비들을 설치하고 가서 고양이 걱정을 덜 한 재밌는 여행이었다. 좋은 숙소에 묵고 맛있는 음식을 많이 먹은 여행이었는데, 숙소에서 일도 꽤 많이 했다.</p>
<hr />
<h2 id="heading-66eo7j2aiouepo2kuoybjo2cueqzvcdrsjztkzw">많은 네트워킹과 발표</h2>
<p>여러 자리에서 사람들을 만나며 느낀 건, 생각보다 많은 사람들이 비슷한 고민을 하고 있다는 점이었다.<br />“AI를 어떻게 잘 써야 할까?”라는 질문이 반복해서 나왔고, 나는 여전히 AI가 버블이라고 생각하지 않는다. 다만 AI를 제대로 쓰기 위해서는, 마법 같은 도구를 기대하기보다 구조와 시스템이 먼저 필요하다고 느꼈다.</p>
<h3 id="heading-1-vercel">1월 말: Vercel 최지원과 페어코딩</h3>
<p>Vercel의 최지원님과 진행한 페어코딩은 협업 자체가 굉장히 재미있었던 경험이었다.<br />미국 회사에서 일하면서 한국인 동료와 함께 코드를 짜는 시간이어서 더 편안하고 즐겁게 느껴졌다.</p>
<h3 id="heading-3-1">3월 1일: 팀 스파르타(항해) 손윤주 씨와 커피챗</h3>
<p>손윤주 씨와의 커피챗에서는 그분이 정말 성실하게 일하는 분이라는 인상을 받았다.<br />대화 중에는 내가 AI 이야기를 많이 했고, 노트북까지 꺼내 직접 데모를 보여주며 이야기를 이어갔다.</p>
<h3 id="heading-3-11-ai">3월 11일: 항해 AI 웨비나</h3>
<p>이 웨비나는 개인적으로 정말 즐거운 경험이었다.<br />평소 주변 사람들에게 “AI는 이제 무조건 써야 한다”고 이야기하고 다녔는데, 그 메시지를 웨비나라는 공식적인 자리에서 정리해 전달할 수 있어서 좋았다. AI 도구들을 소개하며 내가 계속 강조해오던 이야기를 꺼낼 수 있었던 시간이 오래 기억에 남는다.</p>
<h3 id="heading-3-27">3월 27일: 성균관대학교 강연</h3>
<p>원래는 긴장을 안 할 거라고 생각했지만, 교수님들이 지켜보고 계셔서 생각보다 긴장이 됐다.<br />그럼에도 불구하고 교수님들이 흥미롭게 들어주시고 질문까지 해주셔서 강연을 마치고 나서는 꽤 뿌듯한 기분이 들었다.</p>
<h3 id="heading-8-6">8월 6일: 오픈소스 밋업</h3>
<p>오픈소스 밋업에서는 다양한 이야기를 들을 수 있었지만, 한편으로는 아쉬운 지점도 있었다.<br />오픈소스 기여를 마치 개인의 성과나 이력서 한 줄처럼 이야기하는 분위기가 있었는데, 오픈소스는 결국 문제를 함께 해결하기 위해 모이는 공동체라는 점을 다시 생각하게 됐다.</p>
<h3 id="heading-9-16">9월 16일: 크래프톤 정글 팟캐스트</h3>
<p>이 자리에서는 Vercel에서의 경험과 실리콘밸리에서의 일하는 방식, 그리고 AI 시대에 개발자가 어떤 자세를 가져야 하는지에 대해 많이 이야기했다.<br />전하고 싶은 메시지가 많다 보니 조금 정리가 덜 된 채로 이야기를 한 점은 아쉬움으로 남았다.</p>
<h3 id="heading-9-18-marco-ippolito">9월 18일: Marco Ippolito 님의 서울 방문</h3>
<ul>
<li><a target="_blank" href="https://x.com/satanacchio/status/1968670634885500997">https://x.com/satanacchio/status/1968670634885500997</a></li>
</ul>
<p>Marco Ippolito 님과의 만남은 서로의 경험을 나누며 굉장히 유익한 시간이었다.<br />Node 컨트리뷰터분이신데 인사이트가 매우 깊었고, 개인적으로도 많은 것을 배울 수 있었다.</p>
<h3 id="heading-9-30-hoco">9월 30일: HOCO 팟캐스트</h3>
<p>크래프톤 정글 팟캐스트와 비슷한 주제를 다뤘지만, 이번에는 미리 블로그 글로 생각을 정리해 둔 상태에서 이야기를 풀어갈 수 있었다.<br />덕분에 훨씬 정돈된 흐름으로 설명할 수 있었고, 스스로도 잘했다고 느낀 자리였다.</p>
<h3 id="heading-10-24">10월 24일: 토스 프다클</h3>
<p>토스 프다클에서는 사람들이 AI를 사용하는 수준이 생각보다 크게 다르다는 걸 실감했다.<br />그 경험을 통해 내가 앞으로 무엇을 해야 할지, 또 어떤 방식으로 사람들을 도울 수 있을지 감을 잡게 됐다. 동시에 개인적으로도 많은 인사이트를 얻은 의미 있는 시간이었다.</p>
<h3 id="heading-10-30-ai-2">10월 30일: 항해 AI 웨비나 2</h3>
<hr />
<h2 id="heading-6riw7yoaioydtouypo2kua">기타 이벤트</h2>
<h3 id="heading-2">2월 중순: 수면 문제 해결</h3>
<p>내가 원래 잠을 되게 못 자는 사람이었다. 어느 날 우연히 상추차를 접하게 되었는데, 상추차를 마시니까 잠이 잘 왔다. 며칠 실험해보고 이건 진짜라는 것을 깨달았다. 그리고 지금은 상추환으로 정착했다. 상추차를 농축해서 환으로 만들어놓은 것인데, 상추차는 차의 특성상 우리거나 마시는 데에 시간이 걸리고, 물을 많이 마시다보니 아침에 자꾸 깨서 상추환이 훨씬 나았다. 상추환을 꾸준히 먹다보니 수면에 관련된 호르몬 자체가 선순환으로 들어간건지 이제는 낮 시간에도 졸리면 낮잠을 잘 수 있게 됐다. 삶의 질이 아주 높아져서 행복하다.</p>
<h3 id="heading-11">11월 초: 사촌동생 결혼식</h3>
<p>사촌동생 결혼식에 참석했다. 놀랍게도 이게 내가 가 본 첫번째 결혼식이다. 나보다 어린 나이에, 오랜 시간 함께한 연인과 결혼하는 모습을 보니까 부러웠고, 나도 원래 한명하고 오래 연애하다가 27살 쯤에 결혼하고 싶었던 사람이라서 여러 가지 생각이 들었다. 이 이벤트가 나한테 꽤나 큰 변화를 남겨서 회고록에 적는다.</p>
<h2 id="heading-66ei7lmy66mw">마치며</h2>
<p>2025년을 지나온 지금, 예전보다 자유롭고 행복하다. 내 일을 충분히 잘 해내고 있는 것 같아서 뿌듯하기도 하다. 여전히 실험 중이고, 여전히 복잡한 문제를 풀고 있지만, 지금의 상태는 나쁘지 않다고 느낀다.</p>
]]></content:encoded></item><item><title><![CDATA[Ai 시대의 개발자]]></title><description><![CDATA[오늘 발표에서 얘기할 내용들입니다.

기본 태도

적극적으로 사용해야 함. 모든 도구를 사용해볼 필요는 없지만, 좋은 평을 받는 도구는 시도해볼 것.

AI를 적극적으로 활용하는 기업 기준, 현 시점에서 이미 프론트엔드/백엔드 같은 것은 안 중요함.

모든 것을 다 알아야한다는 태도는 반드시 버릴 것. 이것은 개발 씬에서 원래도 문제임. 솔직하게 말하면 개발 소질 이슈. 애매한 중상위권의 특징임.

원리 이해 같은 경우, 난이도가 훨씬 낮지만 ...]]></description><link>https://kdy1.dev/2025-9-30-developer-in-ai-era</link><guid isPermaLink="true">https://kdy1.dev/2025-9-30-developer-in-ai-era</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Mon, 29 Sep 2025 15:00:00 GMT</pubDate><content:encoded><![CDATA[<hr />
<p>오늘 발표에서 얘기할 내용들입니다.</p>
<hr />
<h2 id="heading-6riw67o4io2dnoupha">기본 태도</h2>
<ul>
<li><p>적극적으로 사용해야 함. 모든 도구를 사용해볼 필요는 없지만, 좋은 평을 받는 도구는 시도해볼 것.</p>
</li>
<li><p>AI를 적극적으로 활용하는 기업 기준, 현 시점에서 이미 프론트엔드/백엔드 같은 것은 안 중요함.</p>
</li>
<li><p>모든 것을 다 알아야한다는 태도는 반드시 버릴 것. 이것은 개발 씬에서 원래도 문제임. 솔직하게 말하면 개발 소질 이슈. 애매한 중상위권의 특징임.</p>
<ul>
<li><p>원리 이해 같은 경우, 난이도가 훨씬 낮지만 효율적인 방법은 아님.</p>
<ul>
<li>자기가 국소적 이해 기반의 코딩을 할 수 없다면 전체 설계를 이해해서라도 코딩을 해야함.</li>
</ul>
</li>
<li><p>큰 프로젝트 여러 개를 동시에 해야하는 경우 국소적 이해로 코딩하는 능력이 필수임.</p>
<ul>
<li><p>이 능력을 습득하고 싶으면 규모가 꽤 있지만 본인은 잘 모르는 오픈소스에 기여하는 것이 최고라고 생각.</p>
</li>
<li><p>오픈소스에서 이것이 특히 중요한데, 규모가 큰 프로젝트 여러 개에 각각 패치를 보내야하는 일이 생각보다 자주 있는데 물어볼 사람도 없고, 문서도 내부 구현에 대한 것은 없는 경우가 대부분인데 각 프로젝트를 제대로 이해하고 작업하려고 하면 작업 시간이 지나치게 늘어남.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="heading-67cu7j2067im7l2u65sp7j2yiousuoygncdtlbtqsrdssyu">바이브코딩의 문제 해결책</h2>
<ul>
<li><p>바이브 코딩의 단점을 막을 가장 중요한 해결책은 <strong>테스트</strong></p>
</li>
<li><p>스펙 문서는 항상 넘겨주고 적절한 수정을 요청할 것.</p>
</li>
<li><p>바이브 코딩 이후 작업에 대한 설명이 명확하게 이해가 안 간다면 관련 개념을 공부하는 것을 추천. 그 뒤에도 이해가 안 가면 컴퓨팅적 사고력의 문제.</p>
</li>
</ul>
<h2 id="heading-ai">AI 시대의 능력</h2>
<ul>
<li><p>1인 창업이 맞는 시대라고 보지만 (당연히) 자기 능력 검증은 필수.</p>
</li>
<li><p>AI 없이 처음부터 끝까지 개발하기 위한 능력은 필요치 않음.</p>
</li>
<li><p>다시 말해, <strong>코딩하는 능력</strong> 자체는 이제 의미가 없음.</p>
</li>
<li><p>하지만 AI 를 활용해서 학습하고 코딩하는 능력은 매우 중요함.</p>
<ul>
<li>바이브 코딩 이후 AI의 설명이 이해가 안 간다면 복사해서 다른 인공지능 챗봇에 붙여넣은 뒤 어떤 개념을 공부해야 이 설명을 이해할 수 있는지 물어보고 공부할 것.</li>
</ul>
</li>
</ul>
<p>이 시대의 문제 해결 능력은 2가지</p>
<ul>
<li><p>새로운 문제를 만들지 않는 것</p>
<ul>
<li>핵심은 테스트의 활용</li>
</ul>
</li>
<li><p>주어진 문제를 고치는 것</p>
<ul>
<li><p>AI를 활용해보고, 문제가 생겼을 때 AI를 활용해서 해결한 경험이 많은 것이 중요함.</p>
</li>
<li><p>어떻게 하면 해결되는지는 본인이 전부 기억하고 있어야 함.</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-vs">지능 vs 지식</h2>
<ul>
<li><p>원래 시대가 빠르게 바뀔수록 순수 지능이 중요함. 근데 지능이란 건 함수 같은 것이고 쓰던 사람이 잘 씀. 공부할 때도 머리를 많이 쓰는 방식을 사용하는 것이 좋음.</p>
<ul>
<li><p>바이브 코딩하는 방법을 책이나 강의로 배울 생각을 한다면 바이브코딩 관련 재능 중 제일 중요한 것이 없는 것. 공부 방법을 근본적으로 바꿔야 함.</p>
</li>
<li><p>강의나 책은 자기의 사고력을 덜 쓰는 방법이라서 편하지만 이 시대에 최악인 습관임.</p>
</li>
</ul>
</li>
<li><p>CS 지식 자체는 중요하지 않음. 중요한 건 컴퓨팅적 사고. 이게 처음부터 자유자재로 되는 사람이 있는데 그런 사람은 AI 적극적으로 쓴다는 전제 하에 CS 지식 따로 안 배워도 됨. 근데 이 애기는 천재에 대한 얘기가 아니고 생각보다 이것이 처음부터 되는 사람이 많음. 코테는 컴퓨팅적 사고가 안 되는 사람을 거르는 역할을 함.</p>
</li>
<li><p>코딩하는 능력 자체도 마찬가지로 코딩하는 능력이 없어도 거의 모든 개발이 가능함. 그래서 프로덕트 엔지니어라는 직군으로 바뀌는 것.</p>
</li>
<li><p>메타인지가 오히려 더 중요. 자기가 메타인지가 있는지 확인하는 방법:</p>
<ul>
<li><p>안다고 생각했는데 시험 때 생각 안 났다라는 경험이 한번이라도 있으면 메타인지가 없다고 치고 공부 계획을 짜야함.</p>
</li>
<li><p>자기가 모르는데 안다고 착각하면 해결책 없는 교착 상태가 생김. 안다고 전제하지 말 것.</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-6riw7yoaioygleuzta">기타 정보</h2>
<ul>
<li><p>실리콘밸리의 새로운 변화는 보통 1~2달 정도의 시차를 두고 한국에 들어옴.</p>
<ul>
<li>프로덕트 엔지니어 직군 / AI 네이티브 인재 채용 (카카오) 등</li>
</ul>
</li>
<li><p>코테는 허수 (컴퓨팅적 사고 안 되는 사람들) 거르기 + 면접에 드는 품 줄이기.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[내가 거리를 두고 싶은 사람들]]></title><description><![CDATA[난 이런 사람들과는 거리를 두고싶다. 공격적으로 느껴질 수도 있는 글이라 쓸지 말지 고민 조금 했는데 쓰기로 했다.


사람 급 나누기에 미친 사람들

중범죄자

음주운전 등.


자기의 조그만 이익을 위해 남들한테 큰 피해를 주는 걸 아무렇지 않게 생각하는 사람들.

예를 들어 우회전 할 때 반드시 지나가야하는 위치에 차 세워놓는 사람들.


자기 잘못은 어떻게든 합리화하는 사람들.

남들한테 피해주면서까지 돈 벌려는 사람들.

각종 망상이 ...]]></description><link>https://kdy1.dev/2025-9-29-people</link><guid isPermaLink="true">https://kdy1.dev/2025-9-29-people</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Mon, 29 Sep 2025 00:02:27 GMT</pubDate><content:encoded><![CDATA[<p>난 이런 사람들과는 거리를 두고싶다. 공격적으로 느껴질 수도 있는 글이라 쓸지 말지 고민 조금 했는데 쓰기로 했다.</p>
<hr />
<ul>
<li><p>사람 급 나누기에 미친 사람들</p>
</li>
<li><p>중범죄자</p>
<ul>
<li>음주운전 등.</li>
</ul>
</li>
<li><p>자기의 조그만 이익을 위해 남들한테 큰 피해를 주는 걸 아무렇지 않게 생각하는 사람들.</p>
<ul>
<li>예를 들어 우회전 할 때 반드시 지나가야하는 위치에 차 세워놓는 사람들.</li>
</ul>
</li>
<li><p>자기 잘못은 어떻게든 합리화하는 사람들.</p>
</li>
<li><p>남들한테 피해주면서까지 돈 벌려는 사람들.</p>
</li>
<li><p>각종 망상이 심각한 사람들.</p>
</li>
<li><p>나 이용하려는 게 너무 티나는 사람들.</p>
</li>
<li><p>자기 잘못을 이해하는 능력이 글러먹은 사람들.</p>
</li>
<li><p>윤리의식이 나랑 너무 안 맞는 사람들.</p>
</li>
<li><p>쓰레기 짓 해놓고 지가 머리 잘 썼다고 생각하는 사람들.</p>
</li>
<li><p>자신의 이익을 위해 상황을 변조하는 사람들.</p>
<ul>
<li>일부 사실만 공개하는 것은 제외.</li>
</ul>
</li>
<li><p>남의 돈으로 생색내는 사람들.</p>
</li>
</ul>
<p>이렇게 늘어놓으면 인생 피곤하게 사는 것처럼 들릴 수도 있는데 이 사람들한테 공통점이 있어서 피하는 것이 그리 어렵지는 않은 것 같다.</p>
]]></content:encoded></item><item><title><![CDATA[How to use Claude Code via GitHub]]></title><description><![CDATA[Setup
I configured Claude Code GitHub Action correctly. You should add setup steps to emulate a real development environment before anthropics/claude-code-action@beta. The setup steps depend on the repository, but I’ll provide my Claude Code Action S...]]></description><link>https://kdy1.dev/2025-8-24-claude-code-github</link><guid isPermaLink="true">https://kdy1.dev/2025-8-24-claude-code-github</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Sat, 23 Aug 2025 15:00:00 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-setup">Setup</h2>
<p>I configured Claude Code GitHub Action <em>correctly</em>. You should add setup steps to emulate a real development environment before <code>anthropics/claude-code-action@beta</code>. The setup steps depend on the repository, but I’ll provide my Claude Code Action Setup as a reference.</p>
<p><code>.github/workflows/claude.yml</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Claude</span> <span class="hljs-string">Code</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">issue_comment:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">created</span>]
  <span class="hljs-attr">pull_request_review_comment:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">created</span>]
  <span class="hljs-attr">issues:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">opened</span>, <span class="hljs-string">assigned</span>]
  <span class="hljs-attr">pull_request_review:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">submitted</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">claude:</span>
    <span class="hljs-attr">if:</span> <span class="hljs-string">|
      (github.event_name == 'issue_comment' &amp;&amp; contains(github.event.comment.body, '@claude')) ||
      (github.event_name == 'pull_request_review_comment' &amp;&amp; contains(github.event.comment.body, '@claude')) ||
      (github.event_name == 'pull_request_review' &amp;&amp; contains(github.event.review.body, '@claude')) ||
      (github.event_name == 'issues' &amp;&amp; (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
</span>    <span class="hljs-attr">runs-on:</span> [<span class="hljs-string">'self-hosted'</span>, <span class="hljs-string">'linux'</span>]
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">issues:</span> <span class="hljs-string">read</span>
      <span class="hljs-attr">id-token:</span> <span class="hljs-string">write</span>
      <span class="hljs-attr">actions:</span> <span class="hljs-string">read</span> <span class="hljs-comment"># Required for Claude to read CI results on PRs</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">repository</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">1</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Protoc</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">arduino/setup-protoc@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">repo-token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Node.js</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">./.github/actions/setup-node</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configure</span> <span class="hljs-string">environment</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">pnpm</span> <span class="hljs-string">install</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Claude</span> <span class="hljs-string">Code</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">claude</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">anthropics/claude-code-action@beta</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">claude_code_oauth_token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.CLAUDE_CODE_OAUTH_TOKEN</span> <span class="hljs-string">}}</span>

          <span class="hljs-comment"># This is an optional setting that allows Claude to read CI results on PRs</span>
          <span class="hljs-attr">additional_permissions:</span> <span class="hljs-string">|
            actions: read
</span>
          <span class="hljs-comment"># Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)</span>
          <span class="hljs-comment"># model: "claude-opus-4-20250514"</span>

          <span class="hljs-comment"># Optional: Customize the trigger phrase (default: @claude)</span>
          <span class="hljs-comment"># trigger_phrase: "/claude"</span>

          <span class="hljs-comment"># Optional: Trigger when specific user is assigned to an issue</span>
          <span class="hljs-attr">assignee_trigger:</span> <span class="hljs-string">"claude-bot"</span>

          <span class="hljs-comment"># Optional: Allow Claude to run specific commands</span>
          <span class="hljs-attr">allowed_tools:</span> <span class="hljs-string">"Bash,WebFetch,WebSearch"</span>

          <span class="hljs-comment"># Optional: Add custom instructions for Claude to customize its behavior for your project</span>
          <span class="hljs-comment"># custom_instructions: |</span>
          <span class="hljs-comment">#   Follow our coding standards</span>
          <span class="hljs-comment">#   Ensure all new code has tests</span>
          <span class="hljs-comment">#   Use TypeScript for new files</span>

          <span class="hljs-comment"># Optional: Custom environment variables for Claude</span>
          <span class="hljs-comment"># claude_env: |</span>
          <span class="hljs-comment">#   NODE_ENV: test</span>
</code></pre>
<p>Note <code>allowed_tools: "Bash,WebFetch,WebSearch"</code>. You need it to allow Claude Code to run bash commands.</p>
<h2 id="heading-usage">Usage</h2>
<p>Mention <code>@claude</code> from issues or PRs to instruct it to resolve issues.</p>
<p>Example:</p>
<blockquote>
<p><a class="user-mention" href="https://hashnode.com/@https://github.com/claude">@claude</a> <a target="_blank" href="https://github.com/claude">Implem</a>ent comprehensive unit tests</p>
</blockquote>
<h2 id="heading-pro-tip-you-can-use-local-claude-code-to-create-a-handful-of-issues-quickly">Pro Tip: You can use local Claude Code to create a handful of issues quickly</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755992979320/fae16172-d327-4362-b695-db63dd5f54db.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Tip: TailScale]]></title><description><![CDATA[When developing, you often need to connect to your development computer from your phone. In such cases, you need to connect to the same router or use a program like ngrok to connect to your development computer.
However, there is another method, and ...]]></description><link>https://kdy1.dev/tip-tailscale</link><guid isPermaLink="true">https://kdy1.dev/tip-tailscale</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Tue, 19 Aug 2025 15:00:00 GMT</pubDate><content:encoded><![CDATA[<p>When developing, you often need to connect to your development computer from your phone. In such cases, you need to connect to the same router or use a program like <code>ngrok</code> to connect to your development computer.</p>
<p>However, there is another method, and this one is the most convenient and free. It involves using a virtual private network (VPN) service called <strong>Tailscale</strong>. After logging in and registering your device, each device is assigned an IP address.</p>
<p>After running the server on your development laptop, you can check the IP address of the device in the Tailscale app and connect to it using that IP address, as shown in the screenshot, to access the development server.</p>
<p>This is also the best option when creating a service that only you can access, as you don't need to set up complex external access settings. Install Tailscale on the specific device, deploy the service, and connect. Installing Tailscale on the server can also be done with a single line of script, making it very convenient.</p>
]]></content:encoded></item><item><title><![CDATA[Tip for reducing AI cost]]></title><description><![CDATA[If you have a personal VPS, you can reduce your AI subscription fees by deploying OpenWebUI. By connecting OpenRouter, you can use various models without a fixed subscription fee, paying only for what you use via the API. Multiple users can share acc...]]></description><link>https://kdy1.dev/2025-8-14-tip-for-reducing-ai-cost</link><guid isPermaLink="true">https://kdy1.dev/2025-8-14-tip-for-reducing-ai-cost</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Wed, 13 Aug 2025 15:00:00 GMT</pubDate><content:encoded><![CDATA[<p>If you have a personal VPS, you can reduce your AI subscription fees by deploying OpenWebUI. By connecting OpenRouter, you can use various models without a fixed subscription fee, paying only for what you use via the API. Multiple users can share accounts, and in my case, I created accounts for my family members as well. Combined, my family and I paid a total of <strong>$42.44 for seven months</strong> of usage. Usage details can be viewed in the OpenRouter API Key Console.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755994282891/2a681dfd-0eb8-49c2-a541-70a877b4477a.png" alt class="image--center mx-auto" /></p>
<p>As you can see on the left side of the screenshot, chats can be organized like folders, and there are features such as setting different system prompts for each folder. For example, you can set it to only use official documents for matters related to law or taxation.</p>
<p>With LLM models, connecting OpenRouter allows you to utilize a huge number of models freely. The models shown in the screenshot are ones that I have selected and activated. Since my family also uses it, I thought it might be unclear if there were too many.</p>
<p>When you turn on the web search function in the chat feature, it searches for information and organizes it for you. When the image feature is enabled, it generates images. When numbers appear during the inference process or there is a lot to manage, it uses LLM to code, executes it, and then uses the results to provide answers.</p>
<p>Since it supports PWA, you can add it to the home screen on mobile devices or use the app installation feature in desktop Chrome to use it like a native app.</p>
]]></content:encoded></item><item><title><![CDATA[Tip for GitHub Runners Concurrency]]></title><description><![CDATA[The number of concurrent GitHub Action runners is determined by the plan of the GitHub Org to which the repository belongs, regardless of whether the repository is public. Even though GitHub Actions are unlimited and free for public repositories, the...]]></description><link>https://kdy1.dev/2025-8-12-tip-for-github-runners</link><guid isPermaLink="true">https://kdy1.dev/2025-8-12-tip-for-github-runners</guid><category><![CDATA[GitHub]]></category><category><![CDATA[GitHub Actions]]></category><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Mon, 11 Aug 2025 15:00:00 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755994052423/9cb8ef76-8aa5-419b-85c2-2861ecf0ffa8.png" alt class="image--center mx-auto" /></p>
<p>The number of concurrent GitHub Action runners is determined by the plan of the GitHub Org to which the repository belongs, regardless of whether the repository is public. Even though GitHub Actions are unlimited and free for public repositories, they are still affected. Because of this, SWC uses the Enterprise plan.</p>
<p>If you're running a large open-source project and are having issues with GitHub Actions only running 20 jobs at a time, you can upgrade your plan to Team or Enterprise.</p>
<ul>
<li>Documentation link: <a target="_blank" href="https://docs.github.com/en/actions/reference/limits#job-concurrency-limits-for-github-hosted-runners">https://docs.github.com/en/actions/reference/limits#job-concurrency-limits-for-github-hosted-runners</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How I use Claude Code]]></title><description><![CDATA[Some people asked me on X about the way I use Claude Code, so I'm writing this post. I'm too tired to write a long post, so I keep it short. I use Roo Code with Architecture mode first, with a prompt like

Service XXX is for ...
If anything is ambigu...]]></description><link>https://kdy1.dev/2025-7-7-how-i-use-claude-code</link><guid isPermaLink="true">https://kdy1.dev/2025-7-7-how-i-use-claude-code</guid><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Sun, 06 Jul 2025 15:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Some people asked me on X about the way I use Claude Code, so I'm writing this post. I'm too tired to write a long post, so I keep it short. I use <strong>Roo Code with Architecture mode</strong> first, with a prompt like</p>
<blockquote>
<p>Service XXX is for ...</p>
<p>If anything is ambiguous or you are not sure about it, do not infer but ask me.</p>
</blockquote>
<p>After spending a handful of times for clarifying everything and describing entity relations, I ask it to store it as a markdown file. Typically, it generates a very, very long text. I then follow getting started step of the framework I want to use. The latest one I used is <code>tauri</code>. I do this step manually just because typically it's just a few CLI commands. After creating a directory, I ask Claude Code to</p>
<blockquote>
<p>Implement app in @apps/xxx.</p>
<p>[Paste the design markdown file content]</p>
</blockquote>
<p>You'll get a bare minimum prototype that works with dummy data. Even after getting the initial version, I ask Claude Code for all modifications.</p>
]]></content:encoded></item><item><title><![CDATA[par-core & par-iter: Switchable parallelization for Rust]]></title><description><![CDATA[tl;dr:
I forked rayon to allow switching parallelization library or disable parallelization. See par-iter and par-core.

I'm the creator of the SWC project. I've been using chili the SWC Minifier. chili is a parallelization library with a heartbeat s...]]></description><link>https://kdy1.dev/2025-3-26-par-core-and-par-iter</link><guid isPermaLink="true">https://kdy1.dev/2025-3-26-par-core-and-par-iter</guid><category><![CDATA[Rust]]></category><dc:creator><![CDATA[DongYun Kang]]></dc:creator><pubDate>Wed, 26 Mar 2025 07:46:59 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>tl;dr:</p>
<p>I forked <code>rayon</code> to allow switching parallelization library or disable parallelization. See <a target="_blank" href="https://docs.rs/par-iter">par-iter</a> and <a target="_blank" href="https://docs.rs/par-core">par-core</a>.</p>
</blockquote>
<p>I'm the creator of <a target="_blank" href="https://swc.rs">the SWC project</a>. I've been using <code>chili</code> the SWC Minifier. <code>chili</code> is a parallelization library with a heartbeat scheduling algorithm. I found that it performs far better than <code>rayon</code> for my usecase. But it lacks an API like parallel iterators of <code>rayon</code>. Additionally, I need to use different parallelization libraries for <code>next.js</code> or <code>rspack</code> and disable parallelism for Wasm targets. So I forked <code>rayon</code> and named it <a target="_blank" href="https://docs.rs/par-iter">par-iter</a>. <a target="_blank" href="https://docs.rs/par-iter"><code>par-iter</code></a> is based on <a target="_blank" href="https://docs.rs/par-core/"><code>par-core</code></a>, which allows selecting <code>rayon</code> or <code>chili</code> for parallelism or even disabling parallelism using cargo features.</p>
<p>I prefer to get it merged back to <code>rayon</code> , so I filed <a target="_blank" href="https://github.com/rayon-rs/rayon/issues/1235">an issue on the rayon issue tracker to ask if they are open to PR for it</a>, but I don't think it's likely, considering the package name.</p>
<p>Any ideas, thoughts, and feedback are welcome!</p>
]]></content:encoded></item></channel></rss>