[Integration] failed to refresh token for procore integration(524) - state: failed, error_message: T
RCA: [Integration] failed to refresh token for procore integration(524)
Error Log#
[Integration] failed to refresh token for procore integration(524) - state: failed, error_message: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-04-20T03:57:07.402Z
- 최근 발생: 2026-04-20T03:57:07.402Z
Root Cause Summary#
Procore integration(524)의 access token이 2026-02-17에 만료된 후 약 2개월간 갱신되지 않은 상태에서, 사용자가 POST /api/v1/reviews/2pujqr/integrations/procore/access_token API를 호출하여 token refresh를 시도했다. 이때 ProcoreOperation.refresh_token이 Procore OAuth 서버에 만료된 refresh token을 전송했고, Procore가 invalid_grant (401 Unauthorized)로 거부했다. OAuth refresh token은 일정 기간 미사용 시 자동 만료되는데, 2개월간 사용되지 않아 이미 revoke된 상태였다. 이는 특정 integration(524)에 국한된 문제이며, 동일 시간대 다른 procore integration들은 정상적으로 token refresh에 성공했다.
Technical Analysis#
Code Path#
- Entry point — 사용자가 API 호출:
POST /api/v1/reviews/2pujqr/integrations/procore/access_token
# app/controllers/api/v1/integrations_controller.rb:35-38
def access_token
token = repository_instance.access_token
render_json 200, token
end
- Token 만료 확인 — access token이 10분 이내 만료 예정이면 refresh 시도:
# app/repositories/integration_repository.rb:76-101
def access_token
check_failed_state('Failed to get access token')
# ...
if @model.expired_at < 10.minutes.since
refresh_token
else
# return cached token
end
end
이 integration의 expired_at은 2026-02-17 20:58:20 UTC로, 이미 2개월 전에 만료되었으므로 refresh_token이 호출됨.
- Token refresh 시도 — Procore OAuth 서버에 refresh token 전송:
# app/repositories/integration_repository.rb:103-143
def refresh_token
check_failed_state('Failed to get refresh token')
# ...
token = operation_class.refresh_token(@model.refresh_token, @model.region)
rescue StandardError => e
@model.refresh_token_failed_at = DateTime.now
@model.refresh_token_expired_at = nil
@model.failed_state!
Cupix::Logger.error("[Integration] failed to refresh token for ...")
raise e
end
- Procore OAuth 호출 실패 —
invalid_grant응답:
# app/operations/procore_operation.rb:42-73
def self.refresh_token(refresh_token, region)
data = {
client_id: $OAUTH[:procore][:client_id],
client_secret: $OAUTH[:procore][:client_secret],
grant_type: 'refresh_token',
refresh_token: refresh_token,
redirect_uri: $OAUTH[:procore][:redirect_uri]
}
begin
url = "#{$OAUTH[:procore][:site]}#{$OAUTH[:procore][:refresh_url]}"
response = RestClient.post(url, data)
rescue RestClient::Exception => e
response = JSON.parse(e.response)
Cupix::Logger.error("Procore refresh_token failed: #{response['error']}, ...")
raise Cupix::Errors::Parameter.new(
code: 'ARG10000',
reason: "Procore Authentication failed: #{response['error']}",
message: response['error_description']
)
end
end
Procore API가 invalid_grant를 반환하면 RestClient::Exception이 발생하고, Cupix::Errors::Parameter로 re-raise됨.
- 상태 전이 — integration 상태가
active→failed로 변경:
# app/repositories/integration_repository.rb:135-138
@model.refresh_token_failed_at = DateTime.now
@model.refresh_token_expired_at = nil
@model.refresh_token_response_body = token
@model.failed_state!
failed 상태로 전환되면 이후 check_failed_state guard에 의해 추가 refresh 시도가 차단됨 (integration_repository.rb:309-322).
Log Evidence#
동일 request에서 4개의 로그가 발생했으며, request_id: 8ee61752-6d7a-4b23-b283-8fb7733c3eec로 연결됨.
사용한 Datadog 쿼리:
service:cupixworks-api @request_id:8ee61752-6d7a-4b23-b283-8fb7733c3eec
Log 1 (info) — Token refresh 시도, access token이 2026-02-17에 만료됨을 확인:
[Integration] refresh token for procore integration(524) - state: active, expired_at: 2026-02-17 20:58:20 UTC, refresh_token_expired_at:
refresh_token_expired_at이 비어 있어 refresh token 만료 시점이 추적되지 않았음.
Log 2 (error) — Procore OAuth 서버가 refresh 요청 거부:
Procore refresh_token failed: invalid_grant, message: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. - error: 401 Unauthorized
Log 3 (error) — Integration 상태 failed로 전환 (클러스터 대표 에러):
[Integration] failed to refresh token for procore integration(524) - state: failed, error_message: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
Log 4 (info) — 클라이언트에 400 응답:
[400] POST /api/v1/reviews/2pujqr/integrations/procore/access_token (Api::V1::IntegrationsController#access_token)
추가 검색 결과:
service:cupixworks-api "procore" "refresh" status:info
동일 시간대에 integration(1020), integration(141), integration(966), integration(689) 등 다수의 다른 procore integration들이 정상적으로 token refresh에 성공함. 이 문제는 integration(524)에만 국한됨.
service:cupixworks-api "integration(524)" status:error
최근 7일간 integration(524) 관련 에러는 이 1건만 존재.
Fix Recommendation#
즉시 조치 (Critical)#
이 에러는 수정이 필요한 버그가 아님. Procore OAuth refresh token이 장기간 미사용으로 만료(revoke)된 것은 정상적인 OAuth lifecycle이다. 사용자가 Procore에서 재인증(re-authorize)하면 새 token이 발급되어 integration이 복구된다.
다만 이 에러가 error level로 기록되어 error-sweeper에 포착된 것은 적절하지 않을 수 있다. OAuth token 만료로 인한 invalid_grant는 예상 가능한 운영 시나리오이므로 warn level이 더 적절하다.
app/repositories/integration_repository.rb:140—Cupix::Logger.error를Cupix::Logger.warn으로 변경 검토app/operations/procore_operation.rb:57— 마찬가지로invalid_grant응답에 대해warnlevel 검토
단기 개선 (1주 이내)#
- Refresh token 만료 추적 개선:
refresh_token_expired_at이 비어 있는 경우가 있음 (Log 1에서 확인). Procore refresh token의 실제 만료 기간을 문서에서 확인하고, token 발급 시점에refresh_token_expired_at을 정확히 설정하여 cron job(Cupix::Cron::Integration.renew_before_expiration)이 만료 전에 자동 갱신할 수 있도록 해야 함. - 현재
refresh_token_expired_at이nil이면 cron의refresh_token_due_to_expirescope에서 제외됨 (statable/integration.rb:12). 이로 인해 사용자가 수동으로 API를 호출할 때까지 token이 갱신되지 않고 방치될 수 있음.
장기 개선 (재발 방지)#
failed상태의 integration에 대해 사용자에게 자동 알림(이메일 또는 in-app notification)을 보내 재인증을 유도하는 메커니즘 도입 검토- Integration 건강 상태 대시보드 구축: 오래 만료된 token을 가진 integration을 사전에 식별
Monitoring#
invalid_grant로 인한 token refresh 실패 빈도 모니터링:
service:cupixworks-api "refresh_token failed" "invalid_grant"
failed상태의 integration 수 추적:
service:cupixworks-api "[Integration] failed to refresh token"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial
- 이 에러는 단일 integration(524)에 국한된 OAuth token 만료로, 시스템 버그가 아닌 운영 시나리오임. 사용자 재인증으로 즉시 복구 가능. 로그 레벨 조정(
error→warn)이 유일한 코드 변경 권장 사항임.