Skip to content

Commit 13e18c8

Browse files
bgagentclaude
andcommitted
fix(agent): ruff lint + format for OAuth refresh path
Five lint errors surfaced when CI ran `ruff check --fix` against the Wave C agent changes: - F401 unused `timezone` import in `config.py` (replaced with `timedelta`, which is what's actually needed) - RUF034 useless if-else in the `expires_at` ternary — both branches returned identical strings before the recompute below; flatten into a single straightforward `if expires_in: ... else: ...` block - E501 three line-length violations in `config.py` and `test_config.py` — break the long expressions onto helper-named intermediates Confirmed locally: `ruff check .` clean, `ruff format --check` clean, `pytest tests/test_config.py` 15/15 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 21ad80d commit 13e18c8

2 files changed

Lines changed: 47 additions & 36 deletions

File tree

agent/src/config.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import sys
55
import uuid
6+
from datetime import UTC
67

78
from models import TaskConfig, TaskType
89
from shell import log
@@ -85,7 +86,7 @@ def resolve_linear_api_token(channel_metadata: dict[str, str] | None = None) ->
8586

8687
try:
8788
import json
88-
from datetime import datetime, timezone
89+
from datetime import datetime, timedelta
8990

9091
import boto3
9192
from botocore.exceptions import BotoCoreError, ClientError
@@ -104,7 +105,7 @@ def _is_expiring(expires_at_iso: str, threshold_seconds: int = 60) -> bool:
104105
expiry = datetime.fromisoformat(expires_at_iso.replace("Z", "+00:00"))
105106
except ValueError:
106107
return True
107-
return (expiry - datetime.now(timezone.utc)).total_seconds() < threshold_seconds
108+
return (expiry - datetime.now(UTC)).total_seconds() < threshold_seconds
108109

109110
def _refresh(current: dict) -> dict | None:
110111
try:
@@ -113,12 +114,14 @@ def _refresh(current: dict) -> dict | None:
113114
except ImportError:
114115
return None
115116

116-
body = urllib.parse.urlencode({
117-
"grant_type": "refresh_token",
118-
"refresh_token": current["refresh_token"],
119-
"client_id": current["client_id"],
120-
"client_secret": current["client_secret"],
121-
}).encode("utf-8")
117+
body = urllib.parse.urlencode(
118+
{
119+
"grant_type": "refresh_token",
120+
"refresh_token": current["refresh_token"],
121+
"client_id": current["client_id"],
122+
"client_secret": current["client_secret"],
123+
}
124+
).encode("utf-8")
122125
req = urllib.request.Request(
123126
"https://api.linear.app/oauth/token",
124127
data=body,
@@ -128,32 +131,30 @@ def _refresh(current: dict) -> dict | None:
128131
try:
129132
with urllib.request.urlopen(req, timeout=10) as resp: # noqa: S310
130133
payload = json.loads(resp.read().decode("utf-8"))
131-
except Exception as e: # noqa: BLE001
134+
except Exception as e:
132135
log("WARN", f"resolve_linear_api_token refresh failed: {type(e).__name__}: {e}")
133136
return None
134137

135138
if "access_token" not in payload:
136139
return None
137140

138-
now = datetime.now(timezone.utc)
141+
now = datetime.now(UTC)
142+
# Linear's `expires_in` is documented and reliably sent; if it's
143+
# missing we assume the access token is already valid for as long
144+
# as the refresh-token call took to round-trip — set expiry to now.
145+
if "expires_in" in payload:
146+
future = now + timedelta(seconds=int(payload["expires_in"]))
147+
expires_at_iso = future.replace(microsecond=0).isoformat().replace("+00:00", "Z")
148+
else:
149+
expires_at_iso = now.replace(microsecond=0).isoformat().replace("+00:00", "Z")
139150
next_token = {
140151
**current,
141152
"access_token": payload["access_token"],
142153
"refresh_token": payload.get("refresh_token", current["refresh_token"]),
143-
"expires_at": (
144-
now.replace(microsecond=0).isoformat().replace("+00:00", "Z")
145-
if "expires_in" not in payload
146-
else (now.replace(microsecond=0)).isoformat().replace("+00:00", "Z")
147-
# below replaces the simple form above with the real computation
148-
),
154+
"expires_at": expires_at_iso,
149155
"scope": payload.get("scope", current["scope"]),
150156
"updated_at": now.isoformat().replace("+00:00", "Z"),
151157
}
152-
# Recompute expires_at properly.
153-
if "expires_in" in payload:
154-
from datetime import timedelta
155-
future = now + timedelta(seconds=int(payload["expires_in"]))
156-
next_token["expires_at"] = future.replace(microsecond=0).isoformat().replace("+00:00", "Z")
157158

158159
try:
159160
sm.put_secret_value(SecretId=secret_arn, SecretString=json.dumps(next_token))
@@ -168,7 +169,8 @@ def _refresh(current: dict) -> dict | None:
168169
code = ""
169170
if hasattr(e, "response"):
170171
code = getattr(e, "response", {}).get("Error", {}).get("Code", "") or ""
171-
severity = "ERROR" if code in ("AccessDeniedException", "ResourceNotFoundException") else "WARN"
172+
is_hard_failure = code in ("AccessDeniedException", "ResourceNotFoundException")
173+
severity = "ERROR" if is_hard_failure else "WARN"
172174
log(severity, f"resolve_linear_api_token failed: {type(e).__name__}: {e}")
173175
return ""
174176

agent/tests/test_config.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Unit tests for config.py — build_config and constants."""
22

3-
import sys
3+
from datetime import UTC
44
from unittest.mock import MagicMock, patch
55

66
import pytest
@@ -125,11 +125,11 @@ def test_returns_empty_when_region_missing(self, monkeypatch):
125125

126126
def test_resolves_from_secrets_manager_and_caches_in_env(self, monkeypatch):
127127
"""Happy path: channel_metadata carries the ARN, secret has access_token + future expiry."""
128-
from datetime import datetime, timedelta, timezone
128+
from datetime import datetime, timedelta
129129

130130
monkeypatch.delenv("LINEAR_API_TOKEN", raising=False)
131131
monkeypatch.setenv("AWS_REGION", "us-east-1")
132-
future = (datetime.now(timezone.utc) + timedelta(hours=12)).isoformat().replace("+00:00", "Z")
132+
future = (datetime.now(UTC) + timedelta(hours=12)).isoformat().replace("+00:00", "Z")
133133
token_payload = {
134134
"access_token": "lin_oauth_fresh",
135135
"refresh_token": "lin_refresh_xyz",
@@ -148,10 +148,12 @@ def test_resolves_from_secrets_manager_and_caches_in_env(self, monkeypatch):
148148
"SecretString": __import__("json").dumps(token_payload),
149149
}
150150
with patch("boto3.client", return_value=mock_sm):
151-
assert resolve_linear_api_token({"linear_oauth_secret_arn": "arn:test"}) == "lin_oauth_fresh"
151+
resolved = resolve_linear_api_token({"linear_oauth_secret_arn": "arn:test"})
152+
assert resolved == "lin_oauth_fresh"
152153

153154
# Cached for subsequent reads.
154155
import os as _os
156+
155157
assert _os.environ.get("LINEAR_API_TOKEN") == "lin_oauth_fresh"
156158
# Reset for other tests.
157159
monkeypatch.delenv("LINEAR_API_TOKEN", raising=False)
@@ -172,22 +174,29 @@ def test_returns_empty_on_secrets_manager_access_denied(self, monkeypatch):
172174

173175
def test_falls_back_to_env_var_when_channel_metadata_omits_arn(self, monkeypatch):
174176
"""LINEAR_OAUTH_SECRET_ARN env var is the back-compat fallback."""
175-
from datetime import datetime, timedelta, timezone
177+
from datetime import datetime, timedelta
176178

177179
monkeypatch.delenv("LINEAR_API_TOKEN", raising=False)
178180
monkeypatch.setenv("AWS_REGION", "us-east-1")
179181
monkeypatch.setenv("LINEAR_OAUTH_SECRET_ARN", "arn:from-env")
180-
future = (datetime.now(timezone.utc) + timedelta(hours=12)).isoformat().replace("+00:00", "Z")
182+
future = (datetime.now(UTC) + timedelta(hours=12)).isoformat().replace("+00:00", "Z")
181183
mock_sm = MagicMock()
182184
mock_sm.get_secret_value.return_value = {
183-
"SecretString": __import__("json").dumps({
184-
"access_token": "lin_oauth_envpath",
185-
"refresh_token": "rt", "expires_at": future, "scope": "read",
186-
"client_id": "c", "client_secret": "s",
187-
"workspace_id": "w", "workspace_slug": "s",
188-
"installed_at": "x", "updated_at": "x",
189-
"installed_by_platform_user_id": "u",
190-
}),
185+
"SecretString": __import__("json").dumps(
186+
{
187+
"access_token": "lin_oauth_envpath",
188+
"refresh_token": "rt",
189+
"expires_at": future,
190+
"scope": "read",
191+
"client_id": "c",
192+
"client_secret": "s",
193+
"workspace_id": "w",
194+
"workspace_slug": "s",
195+
"installed_at": "x",
196+
"updated_at": "x",
197+
"installed_by_platform_user_id": "u",
198+
}
199+
),
191200
}
192201
with patch("boto3.client", return_value=mock_sm):
193202
assert resolve_linear_api_token() == "lin_oauth_envpath"

0 commit comments

Comments
 (0)