JSON::ParserError: Empty input (after ) at line 1, column 1 [parse.c:1060] in 'pano_mask_enhanced
RCA: JSON::ParserError Empty input in 'pano_mask_enhanced'
Overview#
What Happened#
cupixworks-api (tesla) 의 PUT /api/v1/captures/{id}/meta/pano_mask_enhanced 엔드포인트(Api::V1::CapturesController#update_meta_by_key)에서 요청 본문(request body)이 비어 있을 때 JSON.parse("") 가 JSON::ParserError: Empty input 를 발생시켰다. 이 예외는 해당 액션의 begin/rescue 가 Cupix::Errors::Parameter 만 포착하도록 되어 있어 unhandled 상태로 전파, HTTP 500 으로 매핑됐다. 2026-05-27 ~ 2026-07-14 사이 총 10건의 저빈도 산발 발생이다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | JSON::ParserError |
| exception.message | Empty input (after ) at line 1, column 1 [parse.c:1060] |
| top_frame | app/controllers/concerns/metable_controller.rb:48 |
| runtime | Ruby (json gem 2.10.1) / Rails |
| env | production/dev (cupixworks-api) |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
cupixworks-api (meta/pano_mask_enhanced 엔드포인트 호출 클라이언트) |
10 (전체 기간) | 빈/malformed body 요청이 400 대신 500 으로 응답됨. 실제 데이터 손상 없음 |
Timeline#
- 2026-05-27 18:01 KST — 최초 발생 (first_seen)
- 2026-07-14 20:46 KST — 최근 발생 (last_seen)
- 2026-08-06 (현재) — RCA 수행. last_seen 이 14일 retention 창 밖이라 현재 로그 0건
Error Log#
Empty input (after ) at line 1, column 1 [parse.c:1060] in 'pano_mask_enhanced'
Impact#
- Service:
cupixworks-api(tesla) - 발생 횟수: 10
- 최초 발생: 2026-05-27 18:01 KST
- 최근 발생: 2026-07-14 20:46 KST
Root Cause Summary#
update_meta_by_key 액션은 JSON.parse(request.raw_post) 로 요청 본문을 파싱한다. 클라이언트가 PUT .../meta/pano_mask_enhanced 를 빈 본문으로 보내면 request.raw_post 가 빈 문자열이 되고, json gem 2.10.1 이 JSON::ParserError: Empty input (after ) at line 1, column 1 [parse.c:1060] 를 발생시킨다. 그러나 이 액션의 begin/rescue 블록은 rescue Cupix::Errors::Parameter 만 포착하도록 되어 있어 JSON::ParserError 는 잡히지 않는다. JSON::ParserError < StandardError 는 client_error_controller.rb / server_error_controller.rb 의 어떤 rescue_from 목록에도 없고(특히 RuntimeError 의 하위가 아니라 502 rescue 에도 미포함), catch-all rescue_from StandardError 도 없어 Rails 기본 처리로 HTTP 500 이 된다. 즉 클라이언트 입력 오류(빈/malformed body, 마땅히 400)가 서버 오류 500 으로 mis-map 된다. 형제 액션 update_meta 는 동일 파싱을 rescue JSON::ParserError → ARG10004 (400) 로 올바르게 처리하고 있어, 이는 두 액션 간 error handling 불일치가 root cause다.
Technical Analysis#
Code Path#
- Entry point:
PUT /api/v1/captures/*/meta/*meta_key→Api::V1::CapturesController#update_meta_by_key(routeconfig/routes.rb:320) - Failure point:
app/controllers/concerns/metable_controller.rb:48
경로: 클라이언트가 meta_key = pano_mask_enhanced 로 빈 본문 PUT → request.raw_post == "" → JSON.parse("") raise.
begin
parsed_meta = JSON.parse(request.raw_post) # 빈 본문이면 JSON::ParserError 발생
@model.meta[params[:meta_key]] = parsed_meta
@model.skip_entrypoint_flush = true if @model.respond_to?(:skip_entrypoint_flush)
@model.save
if CLUSTER_META_LOGGING_ENABLED && @model.instance_of?(::Cluster)
meta_str = parsed_meta.to_json rescue ''
if meta_str.bytesize < 10_000
Cupix::Logger.info("Meta updated by key: #{params[:meta_key]}. Cluster #{@model.id}",
class: @model.class.name,
function: __method__,
module: 'MetableController',
meta: parsed_meta)
end
end
rescue Cupix::Errors::Parameter => e # JSON::ParserError 는 여기서 안 잡힘
raise Cupix::Errors::Parameter.new(code: 'ARG10004', reason: e.to_s, message: e.message)
params[:meta_key] 는 라우트 wildcard *meta_key 로 pano_mask_enhanced 값을 가진다. ET 대표 메시지의 in 'pano_mask_enhanced' 접미사는 이 meta_key 경로 세그먼트를 반영한 것으로, json gem 2.10.1 예외 메시지 자체(Empty input (after ) at line 1, column 1 [parse.c:1060])에는 in '...' 이 포함되지 않는다(Datadog Error Tracking 이 그룹핑 컨텍스트로 부착).
형제 액션 update_meta 는 동일한 파싱을 올바르게 방어한다 — 대조:
begin
@model.meta = JSON.parse(request.raw_post)
@model.skip_entrypoint_flush = true if @model.respond_to?(:skip_entrypoint_flush)
@model.save
rescue JSON::ParserError # 올바르게 포착 → 400
raise Cupix::Errors::Parameter.new(code: 'ARG10004', reason: 'Failed to parse meta value')
기대 동작: 빈/malformed body 는 클라이언트 입력 오류이므로 update_meta 처럼 ARG10004 (HTTP 400) 로 매핑돼야 한다. 실제 동작: update_meta_by_key 는 JSON::ParserError 를 놓쳐 unhandled → HTTP 500.
Cupix::Errors::Parameter 는 400 으로 매핑되지만, 그 앞단의 JSON.parse 실패는 이 클래스가 아니라 raw JSON::ParserError 이므로 rescue 목록 어디에도 걸리지 않는다:
rescue_from Cupix::Errors::Unknown,
Cupix::Errors::Resource,
Cupix::Errors::Session,
Cupix::Errors::Parameter, # JSON::ParserError 는 이 계열이 아님
Cupix::Errors::Entity,
Cupix::Errors::Billing,
Cupix::Errors::InvalidState,
Cupix::Errors::Siteinsights, with: :client_400_error
server_error_controller.rb:18-20 의 502 rescue 는 RuntimeError 만 포함하는데 JSON::ParserError 는 StandardError 직속 하위이지 RuntimeError 하위가 아니라 이 역시 미포착. catch-all rescue_from StandardError 도 없어 Rails 기본 :internal_server_error → 500 이 된다.
Log Evidence#
Datadog 쿼리 (성공 요청 — 엔드포인트/경로 확인):
service:cupixworks-api "pano_mask_enhanced"
정상 요청 로그(엔드포인트/액션/meta_key 경로 세그먼트 확증):
{
"timestamp": "2026-08-06 16:43:07",
"status": "info",
"message": "[200] PUT /api/v1/captures/86578/meta/pano_mask_enhanced (Api::V1::CapturesController#update_meta_by_key)"
}
에러 변형 검색(현재 창 0건 — retention 밖):
service:cupixworks-api status:error "Empty input" "pano_mask_enhanced"
결과: Found 0 logs (last_seen 2026-07-14 은 now-14d 밖). Error Tracking 이 retention 초과 occurrence 를 집계해 이 이슈를 유지.
참고 — 동일 Empty input ... [parse.c:1060] 메시지 포맷이 별개 코드경로(BimAi get failed)에서도 나타나며, 그쪽은 파싱 대상 응답 본문(AWS Elastic Beanstalk Node.js sample HTML)을 in '...' 로 붙인다. 이는 별개 ET 이슈이므로 cross-merge 금지:
service:cupixworks-api status:error "Empty input"
BimAi get failed: Empty input (after ) at line 1, column 1 [parse.c:1060] in '<!DOCTYPE html> ...'
json gem 버전 확인:
Gemfile.lock: json (2.10.1)
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | update_meta_by_key 의 JSON.parse(request.raw_post) 가 빈 본문에서 JSON::ParserError 를 발생시키나 rescue Cupix::Errors::Parameter 만 있어 미포착 → unhandled 500 (client 입력 오류의 status mis-mapping) |
metable_controller.rb:48 JSON.parse(request.raw_post); :62 rescue Cupix::Errors::Parameter 만; client/server error controller rescue 목록에 JSON::ParserError 없음; update_meta:23 는 rescue JSON::ParserError 로 올바르게 처리(불일치); 메시지 Empty input = 빈 문자열 파싱 시그니처; 성공 요청 로그로 엔드포인트/액션 확증 |
— | Confirmed |
| H2 | 대표 메시지가 stale 이고 실제 현재 발생은 pano_mask_enhanced 가 아닌 다른 meta_key/코드경로 |
같은 [parse.c:1060] 포맷이 BimAi get failed 경로에도 존재 |
service:cupixworks-api status:error "Empty input" "pano_mask_enhanced" = 0건이나 이는 retention(14d) 밖이라 부재를 의미하지 않음; pano_mask_enhanced 는 tesla 고유 meta_key 이고 BimAi 경로는 별개 ET 이슈(다른 body 부착) → 우리 이슈의 대표는 endpoint 세그먼트로 신뢰 가능, stale 아님 |
Rejected |
| H3 | meta[:pano_mask_enhanced] 값의 역직렬화(serialize)에서 JSON 파싱 실패 |
Capture 가 pano_mask_enhanced 를 meta 키로 노출 |
meta serialize 는 Cupix::Util::FlexibleHash 로 YAML 사용(flexible_hash.rb:6 YAML.safe_load), JSON 아님; pano_mask_enhanced 참조는 tesla 전체 2파일뿐이며 JSON.parse 없음 |
Rejected |
| H4 | 외부 의존성 장애로 인한 downstream 5xx | — | status-board svc:cupixworks-api::unknown active 인시던트는 2026-08-06 05:42~07:21 UTC 배포 버스트로 이 클러스터 last_seen(2026-07-14)과 무관; 예외가 로컬 JSON.parse 라 downstream 아님 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
app/controllers/concerns/metable_controller.rb:47-63update_meta_by_key의begin/rescue에rescue JSON::ParserError를 추가해 형제 액션update_meta(:23-24) 와 동일하게Cupix::Errors::Parameter (ARG10004)→ HTTP 400 으로 매핑한다. 이렇게 하면 빈/malformed body 라는 클라이언트 입력 오류가 500 이 아닌 400 으로 정확히 반환된다. (기존rescue Cupix::Errors::Parameter절은 유지)
단기 개선 (1주 이내)#
- 두 액션(
update_meta,update_meta_by_key)의 파싱/에러 처리를 공통 헬퍼로 통일해 향후 재발/불일치를 방지한다.parse_meta_value(:73-106) 가 이미 유사한 유연 파싱을 하고 있으므로 raw_post 파싱도 단일 지점으로 수렴시키는 방향을 검토한다.
장기 개선 (재발 방지)#
- 컨트롤러 진입 단계의 body/파라미터 계약 검증(schema validation)을 도입해
JSON.parse전에 빈 본문/형식 오류를 4xx 로 일관되게 처리한다. 광범위rescue_from StandardError는 실제 nil-ref 등 진짜 버그를 가리므로 지양하고, 파싱 실패만 좁게 4xx 로 매핑한다.
Monitoring#
수정 후 이 엔드포인트에서 500 이 사라지고 400(ARG10004)로 전환되는지 확인.
파싱 실패(수정 후 400)로 재분류되는 요청 추이:
service:cupixworks-api "ARG10004" "meta"
meta/pano_mask_enhanced 엔드포인트의 5xx 응답(수정 후 0 이어야 함):
service:cupixworks-api "meta/pano_mask_enhanced" ("[500]" OR status:error)
Empty input 파싱 예외 재발 감지:
service:cupixworks-api status:error "Empty input" "pano_mask_enhanced"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (한 액션에
rescue JSON::ParserError절 1개 추가, 형제 액션에 이미 존재하는 패턴 차용)
Noise Verdict#
noise — 빈/malformed 요청 본문이라는 클라이언트 입력 오류가 status-code mis-mapping 으로 500 이 됐을 뿐 데이터 손상이나 서버 로직 결함이 없고, 조치는 4xx 재매핑(품질 개선)이다.