From 48b81a8a3d14616a02e206c780668ea955d26b14 Mon Sep 17 00:00:00 2001 From: Anthony Breckner Date: Tue, 18 Aug 2026 10:40:11 +0800 Subject: [PATCH 1/3] ci: Restore a working build CI has failed on master since July and last passed in March. The build dies during setup, so no test has actually run in months. Three separate breakages, each fixed here: actions/setup-ruby@v1 is deprecated and can no longer resolve "3.1.x", failing the job outright. Replaced with the maintained ruby/setup-ruby@v1, which also handles bundle install and caching. The lockfile pinned BUNDLED WITH 2.1.4. Bundler re-execs into the version named there, and 2.1.4 cannot load "socket" on current rubygems, so it crashes before installing anything. Dropping the pin lets the installed bundler run; the workflow asks for the latest. actions/checkout@v2 runs on a deprecated Node. Ruby stays on 3.1 deliberately. The pinned dependencies do not survive newer runtimes: activesupport 7.1.1 needs "logger", dropped from the default gems in Ruby 4.0, and parser 3.2.0.0 needs "racc", dropped in 3.3. Moving off 3.1 means upgrading those first, which is its own change. Co-authored-by: Claude Opus 5 --- .github/workflows/ruby.yml | 20 ++++++++++---------- Gemfile.lock | 3 --- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 074b4c4..1422d59 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -12,14 +12,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Ruby 3.1 - uses: actions/setup-ruby@v1 + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 with: - ruby-version: 3.1.x - - name: Build and test with Rake - run: | - gem install bundler - bundle install --jobs 4 --retry 3 - bundle exec rubocop - bundle exec rake + ruby-version: '3.1' + bundler: latest + bundler-cache: true + - name: Lint + run: bundle exec rubocop + - name: Test + run: bundle exec rake diff --git a/Gemfile.lock b/Gemfile.lock index 9717cbb..1dc81cc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -125,6 +125,3 @@ DEPENDENCIES rubocop-rspec vcr xpm_ruby! - -BUNDLED WITH - 2.1.4 From 92808caf6bbc57aadf515159ebcdd73d5024e1ec Mon Sep 17 00:00:00 2001 From: Anthony Breckner Date: Tue, 18 Aug 2026 10:42:50 +0800 Subject: [PATCH 2/3] ci: Sync the lockfile with the gemspec version The lockfile recorded xpm_ruby 0.4.0 while the gemspec resolves 0.4.1, so bundler refuses to install under the frozen mode CI runs in. Co-authored-by: Claude Opus 5 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1dc81cc..b71d2f0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - xpm_ruby (0.4.0) + xpm_ruby (0.4.1) activesupport builder dry-types From 69e4f5e19d606e2ef02b56b8b9e60026b1e9af6d Mon Sep 17 00:00:00 2001 From: Anthony Breckner Date: Tue, 18 Aug 2026 10:46:38 +0800 Subject: [PATCH 3/3] Extract the error detail lookup in handle_response handle_response parsed the response body for "Detail" in three separate branches, which put it over the AbcSize limit. The lint failure has been there since the 403 branch was added, unseen because the build was already dying during setup. Behaviour is unchanged. Co-authored-by: Claude Opus 5 --- lib/xpm_ruby/connection.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/xpm_ruby/connection.rb b/lib/xpm_ruby/connection.rb index 0691ab6..6af8700 100644 --- a/lib/xpm_ruby/connection.rb +++ b/lib/xpm_ruby/connection.rb @@ -71,7 +71,7 @@ def xpm_url def handle_response(response) case response.status when 401 - detail = JSON.parse(response.body)["Detail"] + detail = error_detail(response) case detail when /TokenExpired: token expired/ @@ -80,15 +80,14 @@ def handle_response(response) raise Unauthorized.new(detail) end when 403 # this can happen with a bad xero_tenant_id - detail = JSON.parse(response.body)["Detail"] + detail = error_detail(response) raise AuthenticationUnsuccessful.new(detail) if detail == "AuthenticationUnsuccessful" raise Forbidden.new(detail) when 500 raise InternalServerError.new(response.reason_phrase) when 503 - detail = JSON.parse(response.body)["Detail"] - raise NotAvailable.new(detail) + raise NotAvailable.new(error_detail(response)) when 429 # rate limit exceeded details = response.headers.slice( "retry-after", @@ -111,5 +110,9 @@ def handle_response(response) raise UnknownError.new(response.status) end end + + def error_detail(response) + JSON.parse(response.body)["Detail"] + end end end