agent-harness 0.36.7 → 0.36.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 787476d24b2577b593c52b21805ddbbc3e7f8fec95eec0fda829d5c382ebf616
4
- data.tar.gz: c3645ffa8fdaaf4ce2d6df5dd58417f3f37b1a557351e3abcb074714a547296f
3
+ metadata.gz: 7b5a9c9ca2e2b03184fad17fea28af20996a3710d23ed5e34feed302be6f54b8
4
+ data.tar.gz: 888c84fe0cc58a5d9f20a0f396bb75e429fc84287fcb5e4e337340841d2d70a0
5
5
  SHA512:
6
- metadata.gz: 76d24a618b0cfea4fc91a0e81ca4999adbbc86c3db8ba7fd18baff97956c147853af1daf314a5bb98e104d2f6f502c0a06d5aceb1fb13139b9b083f813a43b09
7
- data.tar.gz: 97d8c0362dcb3badbed261fca466ed16d8206e3141c229a3eca9bc00240d93420d8170e73575c8b21c1676dfd22047d85f099163677a229412db52e5094ee0f3
6
+ metadata.gz: 93eadc79037f9d847ad418023ed8ed9d4920e32445d63e39f4a81b951a65f9587386bf1d2012b632c644f73b7173e4471578160161063570f93c6f8781d671d3
7
+ data.tar.gz: 6f7e1a3084562f773e415b9bbc23157f3151308e4995a5bb7812ef8080cd871d21da581371f550c6abeff4485c13ad75fd1d873bbb26d76715b2805edd16a1a2
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.36.7"
2
+ ".": "0.36.8"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@
5
5
  * add runner model compatibility contract (`AgentHarness.model_compatibility`) with structured `ModelCompatibility::Result` outcomes. Codex exposes static facts for CLI-gated models (e.g. `gpt-5.5` requires Codex CLI `>= 0.116.0`), a baseline supported-model list, supported auth modes, and a `DEFAULT_COMPATIBLE_MODEL_ID` fallback so downstream orchestrators can validate tier/model assignments before scheduling agent runs ([#259](https://github.com/viamin/agent-harness/issues/259)).
6
6
  * **auth:** add provider-owned PKCE code-exchange API for Claude OAuth (`AgentHarness::Authentication.exchange_code`). Takes an authorization code plus PKCE verifier (and `redirect_uri`/`client_id`), posts an `authorization_code` grant to the Claude token endpoint, and persists the resulting access/refresh tokens in the native `claudeAiOauth` shape. Adds `exchange_code_supported?` and a `code_exchange` key to `auth_capabilities` ([#266](https://github.com/viamin/agent-harness/issues/266)).
7
7
 
8
+ ## [0.36.8](https://github.com/viamin/agent-harness/compare/agent-harness/v0.36.7...agent-harness/v0.36.8) (2026-08-25)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Claude CLI Not logged in responses should raise AuthenticationError ([#372](https://github.com/viamin/agent-harness/issues/372)) ([8782aef](https://github.com/viamin/agent-harness/commit/8782aef42b0ec6f8e550dc98e79d316bb58b6044))
14
+
8
15
  ## [0.36.7](https://github.com/viamin/agent-harness/compare/agent-harness/v0.36.6...agent-harness/v0.36.7) (2026-08-25)
9
16
 
10
17
 
@@ -216,6 +216,25 @@ module AgentHarness
216
216
 
217
217
  private
218
218
 
219
+ # Message fragments the Claude CLI emits when the local session is
220
+ # missing or expired. Detected in the envelope's +result+ field even
221
+ # when +subtype+ is "success", since Claude reports the failure
222
+ # inside the payload rather than through the transport layer.
223
+ #
224
+ # Scoped to explicit expired / login-required phrases. A bare
225
+ # "authentication" match would also fire on transient service
226
+ # outages like "authentication service was unavailable", which
227
+ # belong on the retry / provider-fallback path, not the re-auth
228
+ # path.
229
+ AUTH_FAILURE_FRAGMENTS = [
230
+ "oauth token",
231
+ "not logged in",
232
+ "please run /login",
233
+ "login required",
234
+ "credentials expired",
235
+ "session expired"
236
+ ].freeze
237
+
219
238
  def classify_error_message(message)
220
239
  msg_lower = message.downcase
221
240
 
@@ -223,13 +242,20 @@ module AgentHarness
223
242
  "Rate limit exceeded"
224
243
  elsif msg_lower.include?("deprecat") || msg_lower.include?("end-of-life")
225
244
  "Model deprecated"
226
- elsif msg_lower.include?("oauth token") || msg_lower.include?("authentication")
245
+ elsif authentication_failure_message?(msg_lower)
227
246
  "Authentication error"
228
247
  else
229
248
  message
230
249
  end
231
250
  end
232
251
 
252
+ def authentication_failure_message?(message)
253
+ return false if message.nil? || message.empty?
254
+
255
+ msg_lower = message.downcase
256
+ AUTH_FAILURE_FRAGMENTS.any? { |fragment| msg_lower.include?(fragment) }
257
+ end
258
+
233
259
  def extract_tokens(parsed)
234
260
  usage = parsed["usage"]
235
261
  return nil unless usage
@@ -654,22 +680,38 @@ module AgentHarness
654
680
  def parse_response(result, duration:)
655
681
  output = result.stdout
656
682
  error = nil
683
+ auth_source = nil
657
684
  tokens = nil
658
685
  metadata = {}
659
686
 
687
+ parsed = parse_json_output(output)
688
+
689
+ # Claude CLI can return `subtype: "success"` with `is_error: true`
690
+ # (e.g. "Not logged in · Please run /login"); trust `is_error` and the
691
+ # process exit code rather than the envelope subtype.
692
+ envelope_error = parsed && parsed["is_error"]
693
+ envelope_message = parsed && parsed["result"]
694
+
660
695
  if result.failed?
661
696
  combined = [result.stdout, result.stderr].compact.join("\n")
662
697
  error = classify_error_message(combined)
698
+ # With an envelope present, restrict auth detection to stderr so
699
+ # assistant `result` text mentioning "authentication" can't trip
700
+ # a false positive. Without an envelope, the combined output is
701
+ # our only window into CLI-reported auth failures.
702
+ auth_source ||= parsed ? result.stderr : combined
663
703
  end
664
704
 
665
- # Parse JSON output to extract result text, token usage, and metadata
666
- parsed = parse_json_output(output)
667
- if parsed
668
- # Handle is_error envelopes as provider errors
669
- if parsed["is_error"]
670
- error ||= classify_error_message(parsed["result"] || "Unknown Claude CLI error")
671
- end
705
+ if envelope_error
706
+ error ||= classify_error_message(envelope_message || "Unknown Claude CLI error")
707
+ # Prefer the envelope's own message for auth detection to avoid
708
+ # false positives from assistant text that mentions "authentication".
709
+ auth_source = envelope_message
710
+ end
672
711
 
712
+ raise_if_authentication_failure(auth_source)
713
+
714
+ if parsed
673
715
  output = parsed["result"] || output
674
716
  tokens = extract_tokens(parsed)
675
717
  metadata = extract_envelope_metadata(parsed)
@@ -786,6 +828,24 @@ module AgentHarness
786
828
  self.class.send(:classify_error_message, message)
787
829
  end
788
830
 
831
+ def authentication_failure_message?(message)
832
+ self.class.send(:authentication_failure_message?, message)
833
+ end
834
+
835
+ # Raise AuthenticationError so callers (e.g. Conductor) can route to
836
+ # re-auth handling instead of retrying through generic provider-error
837
+ # paths. Fires when the source message (envelope's +result+ field, or
838
+ # the raw combined process output when there is no envelope) matches
839
+ # a Claude "not logged in" / session-expired signal.
840
+ def raise_if_authentication_failure(source_message)
841
+ return unless authentication_failure_message?(source_message)
842
+
843
+ message = source_message.to_s.strip
844
+ message = "Claude CLI reported an authentication failure" if message.empty?
845
+
846
+ raise AuthenticationError.new(message, provider: self.class.provider_name)
847
+ end
848
+
789
849
  def parse_claude_mcp_output(output)
790
850
  servers = []
791
851
  return servers unless output
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AgentHarness
4
- VERSION = "0.36.7"
4
+ VERSION = "0.36.8"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: agent-harness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.36.7
4
+ version: 0.36.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan