Amazon Q에 기여해보기
다양한 AI 개발 도구를 사용하며 Amazon Q Developer CLI에 기여하게 된 과정과 경험을 상세히 공유합니다.
Contents
배경
최근 Vibe Coding 이라는 단어가 아주 자주 보인다. 나 또한 어떻게 AI 를 통해 개발생산성을 높일지 고민하고 있었다. 가장 쉬우면서도 기초적인 방법은 다양한 AI 모델들과 Agent 를 사용해 보는 것이었다. Github Copilot 을 시작으로, Claude MCP, Claude Code, Cursor 등의 AI Assistant 제품들을 사용했다. AI Assistant 의 기능은 Cursor, Claude Code 가 가장 마음에 들었지만, 아쉽게도 두 도구들은 단점이 하나씩 있었다.
Cursor

먼저 Cursor 는 Kotlin 개발을 하기 어렵다는 단점이 있었다. 업무상으로 Kotlin, Java, Spring 을 사용해야 할 일이 종종 있었는데, Kotlin 을 Cursor 에서 사용하는 것은 까다로웠다. 엄밀히 말해서는, Kotlin 은 Jetbrain 이 아닌 IDE/Editor 에서 사용하기가 까다롭다. 왜냐하면 효과적으로 동작하는 LSP 서버가 존재하지 않기 때문이다. VSCode 나 Neovim 에 LSP 를 설정한 후 코틀린 파일 여러개를 열어보면, 팬이 돌기 시작하거나 컴퓨터가 멈춰버리는 현상이 자주 발생한다.
Claude Code

Claude Code 는 기능적으로 가장 마음에 드는 제품이었다. Cursor 를 쓰며 느낀 점은, AI Agent 가 IDE 에 강하게 결합되어 있지 않았으면 좋겠다는 것이었다. IDE 를 변경해도 쉽게 사용할 수 있는 AI Agent 는 CLI AI Agent 라고 생각했다. Claude Code 는 이러한 조건을 만족했고, 개인적으로는 가장 똑똑한 AI Agent 라고 생각했다. 하지만 Claude Code 의 단점은 비용이 많이 발생한다는 것이다. Claude Code 는 기본적으로 Claude API 를 통해 동작하기 때문에, 업무에 사용하다 보면 비용이 많이 발생한다. 최근에 Anthropic 에서 Claude Max Plan 사용자들에게는 Claude Code 를 구독제와 함께 사용할 수 있도록 정책을 변경했다. 하지만 Claude Max Plan 은 $100/month 여서, 이 또한 부담스러운 가격이다.
OpenAI Codex

다음으로 시도해 본 제품은 OpenAI Codex 이다. Codex 는 OpenAI 에서 운영하는 CLI AI Agent 오픈소스이다. Codex 는 OpenAI, Claude, Gemini 등의 다양한 AI API 를 사용할 수 있다. 하지만 아직 AI Agent 의 정확성이 낮고, Context 등록 UX 도 불편해 사용하지 않게 되었다.
Amazon Q Developer CLI

앞으로는 또 어떻게 변경될 지 모르지만, 현재 자주 사용하는 AI Agent는 Amazon Q Developer CLI 이다. Amazon Q Developer CLI 는 Amazon Bedrock 을 기반으로 동작하는 AI Agent 이다. Amazon Q Developer 는 VSCode 와 Jetbrain 제품들에서도 사용할 수 있지만, 개인적으로는 CLI 의 성능이 더 편리하고 똑똑하다고 느꼈다.
불편함
Amazon Q Developer CLI 를 사용하다, 불편한 점을 하나 발견하게 되었다. 그것은 특정 파일을 현재 세션의 Context 로 등록하는 과정에서 발생했다. Amazon Q Developer CLI 는 Fuzzy Finder 를 통해 특정 파일을 CLI 에서 검색하고, Context 에 등록하는 기능을 제공한다. 하지만 Fuzzy Finder 에서 다음과 같은 선택지가 나오는 것을 발견할 수 있었다.

무엇이 문제일까? 바로 .gitignore 에 등록된 파일들도 모두 Fuzzy Finder 에 노출된다는 점이다. 이것이 불편하다고 느꼈던 점은 다음의 두 가지 이유 때문이다.
첫째로, 개발자는 실수로 잘못된 파일들을 Context 로 입력할 수 있다. 예를들어, 대부분의 컴파일/트랜스파일 언어가 빌드 후의 결과물이 원본 소스파일과 비슷한 파일명을 지닌다. 예를 들어 Typescript 파일을 트랜스파일하면, 원본 TS 파일과 유사한 파일명이 생성된다. 만약 원본 파일과 트랜스파일된 파일이 모두 Fuzzy Finder 에 노출된다면, 실수로 잘못된 파일을 Context 로 등록할 수 있다. 개발자들은 보통 빌드 디렉토리를 .gitignore 에 등록하기 때문에, .gitignore 에 있는 파일을 Fuzzy Finder 에서 노출시키지 않는 것이 좋다고 생각했다.
둘째로, .gitignore 에 있는 파일들을 모두 Fuzzy Finder 에 노출하는 것은 보안적으로 좋지 않다고 생각했다. 개발자들은 종종 .gitignore 에 비밀정보가 담긴 파일들을 등록한다. 예를 들어, AWS Secret Key, DB Password, API Key 등과 같은 비밀정보들이 있을 수 있다. 이러한 비밀정보들이 Fuzzy Finder 에 노출된다면, 실수로 비밀정보가 노출될 수 있다. 따라서 .gitignore 에 있는 파일들은 Fuzzy Finder 에 노출되지 않는 것이 좋다고 생각했다.
기여
이러한 불편함을 어떻게 해결할 수 있을지 찾아보다, Amazon Q Developer CLI 가 오픈소스로 운영되고 있다는 사실을 알게 되었다. 해당 레포지토리를 클론한 후 코드를 구경해 보다가, 다음의 소스코드에서 문제를 발생시킨다는 사실을 알 수 있었다.
/// Select files using skim
pub fn select_files_with_skim() -> Result<Option<Vec<String>>> {
// Create skim options with appropriate settings
let options = create_skim_options("Select files: ", true)?;
// Create a command that will be executed by skim
// This avoids loading all files into memory at once
let find_cmd = "find . -type f -not -path '*/\\.*'";
// Create a command collector that will execute the find command
let item_reader = SkimItemReader::default();
let items = item_reader.of_bufread(BufReader::new(
std::process::Command::new("sh")
.args(["-c", find_cmd])
.stdout(std::process::Stdio::piped())
.spawn()?
.stdout
.ok_or_else(|| eyre!("Failed to get stdout from command"))?,
));
// Run skim with the command output as a stream
match run_skim_with_options(&options, items)? {
Some(items) if !items.is_empty() => {
let selections = extract_selections(items);
Ok(Some(selections))
},
_ => Ok(None), // User cancelled or no selection
}
}
위의 함수 중 find . -type f -not -path '*/\\.*' 의 코드는 현재 디렉토리에 있는 모든 파일들을 검색하기 때문에, .gitignore 에 등록된 파일들도 모두 출력하게 된다. 해당 프로젝트에 흥미도 느끼기도 했고, 더 편하게 Amazon Q Developer CLI 를 사용하기 위해 이 프로젝트에 Pull Request 를 보내보기로 했다.
하지만 한 가지 문제점이 있었다. 해당 프로젝트는 대부분이 Rust 로 작성되어 있는데, 나는 Rust 의 문법을 잘 알지 못했다. 예전에 Web Assembly 에 잠깐 관심을 가졌을 때, Rust 와 Cargo 만을 설치해 보았을 뿐이었다. 하지만 Rust 의 문법을 잘 알지 못해도, AI Agent 를 믿고 Vibe Coding 을 해보기로 했다. 변경하고자 하는 수정사항이 간단하다고 생각했기 때문에, 크게 부담이 되지는 않았다. Amazon Q Developer CLI 의 소스코드를 Amazon Q Developer CLI를 이용해서 작업을 시작했다. 20분 정도 후에 소스코드 변경이 끝났고, 30분정도 로컬에서 테스트를 진행한 후 Pull Request 를 생성했다.
Release
몇일 후에 Contributer 와 짧게 대화를 하며 소스코드를 조금씩 수정했고, Pull Request 는 머지되었다. 그동안 오픈소스로 운영되던 라이브러리에는 몇 번 기여를 했었지만, Amazon 의 제품에 내가 오픈소스로 기여를 하게 되어서 뿌듯함을 느꼈다. Pull Request 가 머지된 후 2주정도의 시간이 지났을 때, 회사에 출근했더니 다음과 같은 화면을 볼 수 있었다.

내 Pull Request 가 Amazon Q 앱의 Release Note 에 적혀있는 것이었다 ! 나의 작업물이 제품에 잘 반영되었는지 바로 확인해 보았다. 괜히 뿌듯해서 직접 수정하면 더 빠른 작업도 일부러 Agent 를 이용해 작업했다. 앞으로도 종종 프로젝트를 살펴보고 기여할 부분이 있다면 또 기여를 해봐야겠다고 생각했다.

AmazonQ 공식문서에도 나의 작업내용이 업데이트 되어있었다..!
또다른 버그
(2025년 10월 업데이트)
이 글을 쓸 당시와는 달리, Claude Code 는 Pro plan을 출시했다. 아주 잘 만들어진 CLI 제품이어서 잘 사용하고 있다. 하지만 Rate Limit에 걸리는 경우가 종종 있어서, 마음 편하게 사용할 수는 없다. 이에 비해, 아직까지 Amazon Q Developer CLI는 Sonnet 모델을 유료 사용자에게는 무제한으로 제공하고 있다. 물론 비정상적으로 많이 사용하는 유저에게는 Monthly 사용제한이 있다고 하지만, 아직까지 한 번도 이 제한에 걸려본 적은 없다.
하지만 Amazon Q Developer CLI에 아쉬운 점은 기능 업데이트가 느리고, 버그가 발견될 때가 있다는 점이다. 최근에도 버그를 몇 개 발견해서, 코드를 수정했다. 다행인 점은, 프로젝트가 오픈소스로 공개되어 있어 버그를 수정 제안할 수 있다는 점이다.
- fix:bug(chat): ensure sanitizeuserprompt handles UTF-8 boundaries correctly
- fix:bug(checkpoint): fix crashes when truncating checkpoint message
- fix:bug(prompts): fix error when truncating prompt description written in multibyte characters
세 번의 Pull Request를 열었지만, 놀랍게도 원인은 모두 동일하다. 프로젝트에 문자열을 부분문자열로 자르는 코드들이 있는데, CJK문자와 같은 Multibyte Character 들에서 발생하는 문제이다.
fn main() {
let english: &str = "Hello World";
let english_substring = &english[0..5];
println!("{}", english_substring);
}
위의 코드를 실행하면 어떤 결과가 나올까?
어렵지 않게 "Hello" 가 출력될 것을 예상할 수 있다. 그렇다면 다음은 어떨까 ?
fn main() {
let korean: &str = "안녕하세요";
let korean_substring = &korean[0..2];
println!("{}", korean_substring);
}
많은 고차원 언어에서는 "안녕"이라는 결과가 출력되지만, Rust에서는 Byte단위로 접근하므로 에러가 발생한다. 한글은 UTF-8 인코딩 환경에서 3바이트를 차지하므로, $korean[0..2] 를 실행하면 "안"의 가운데에서 글자가 잘리게 되는 것이다.
thread 'main' panicked at src/main.rs:30:35:
byte index 2 is not a char boundary; it is inside '안' (bytes 0..3) of `안녕하세요`
stack backtrace:
panic을 발생시키지 않고, 부분문자열을 자르려면 다음과 같은 함수가 필요하다.
/// Truncate by **character count** (Unicode scalar values).
fn truncate_by_chars(s: &str, max_chars: usize) -> &str {
if max_chars == 0 { return ""; }
let idx = s.char_indices()
.map(|(i, _)| i)
.nth(max_chars)
.unwrap_or(s.len());
&s[..idx]
}
혹은 부분문자열을 바이트 단위로 자르고 싶다면, 다음과 같은 함수를 사용하면 된다.
pub fn truncate_safe(s: &str, max_bytes: usize) -> &str {
if s.len() <= max_bytes {
return s;
}
let mut byte_count = 0;
let mut char_indices = s.char_indices();
for (byte_idx, _) in &mut char_indices {
if byte_count + (byte_idx - byte_count) > max_bytes {
break;
}
byte_count = byte_idx;
}
&s[..byte_count]
}
Amazon Q Developer CLI 를 주로 개발하는 작업자들은 주로 영어로 소통해서인지, 이런 단순한 에러를 발생시키는 코드들이 꽤 있었다. 자주 사용하는 AI 도구의 코드를 직접 읽고 fix PR을 날려보니 재미있었다.
이것도 읽어보세요