Conversation
Feat/jira service token
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR migrates Jira authentication from email-based HTTP Basic auth to Atlassian service-account Bearer tokens routed through the Atlassian Cloud API gateway. The adapter now discovers and caches a cloudId per tenant to construct API URLs and rewrite attachment downloads via the Bearer-authenticated ChangesJira Bearer Token and CloudId Migration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/adapters/issue-tracker/jira.ts`:
- Around line 42-57: The discoverCloudId method can hang indefinitely; add a
fetch timeout using an AbortController inside discoverCloudId (and a short
configurable constant e.g. CLOUD_ID_TIMEOUT_MS) so the request to
`${this.tenantOrigin}/_edge/tenant_info` is aborted after the timeout; pass the
controller.signal into fetch, clear the timeout on success, handle the abort by
throwing a descriptive Error (preserving the original fetch error) and ensure no
resource leaks. Use the existing function name discoverCloudId to locate the
change and keep behavior the same when fetch succeeds.
- Around line 206-210: The rewriteIfTenant method incorrectly preserves a
leading "/jira" path segment from tenant attachment URLs, causing duplicated
"/jira" when building the gateway URL; update rewriteIfTenant (alongside
ATLASSIAN_API_ORIGIN and getCloudId usage) to normalize parsed.pathname by
stripping a leading "/jira" segment if present (e.g. remove a leading "/jira"
only once before concatenation) and then return
`${ATLASSIAN_API_ORIGIN}/ex/jira/${cloudId}${normalizedPath}${parsed.search}` so
attachment URLs become `/ex/jira/<cloudId>/rest/...` instead of
`/ex/jira/<cloudId>/jira/rest/...`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6aef8d7a-b357-4f31-98f8-9eceeb1a03a9
📒 Files selected for processing (18)
.claude/skills/init-env/SKILL.md.claude/skills/init-jira/SKILL.md.claude/skills/init-jira/references/column-statuses.md.claude/skills/init-jira/references/transitions.md.env.e2e.example.env.example.github/workflows/ci.yml.github/workflows/e2e.ymlSETUP.mde2e/env.tse2e/helpers/jira.tse2e/tier2/us02-attachments.test.tsenv.test.tsenv.tssrc/adapters/issue-tracker/jira.test.tssrc/adapters/issue-tracker/jira.tssrc/lib/adapters.tssrc/lib/step-adapters.ts
💤 Files with no reviewable changes (8)
- src/lib/adapters.ts
- src/lib/step-adapters.ts
- .github/workflows/e2e.yml
- env.ts
- e2e/env.ts
- env.test.ts
- e2e/tier2/us02-attachments.test.ts
- .github/workflows/ci.yml
| private async discoverCloudId(): Promise<string> { | ||
| const url = `${this.tenantOrigin}/_edge/tenant_info`; | ||
| const res = await fetch(url); | ||
| if (!res.ok) { | ||
| throw new Error( | ||
| `Jira cloudId discovery failed: ${res.status} ${res.statusText} on ${url}`, | ||
| ); | ||
| } | ||
| const data = (await res.json()) as { cloudId?: unknown }; | ||
| if (typeof data?.cloudId !== "string" || data.cloudId === "") { | ||
| throw new Error( | ||
| `Jira cloudId discovery: missing cloudId in ${url} response`, | ||
| ); | ||
| } | ||
| return data.cloudId; | ||
| } |
There was a problem hiding this comment.
Put a timeout on cloudId discovery.
Every cold Jira request now blocks on /_edge/tenant_info. Without a timeout here, a stalled Atlassian edge call can hang the whole adapter path indefinitely.
Suggested fix
private async discoverCloudId(): Promise<string> {
const url = `${this.tenantOrigin}/_edge/tenant_info`;
- const res = await fetch(url);
+ const res = await fetch(url, {
+ signal: AbortSignal.timeout(10_000),
+ });
if (!res.ok) {
+ await res.body?.cancel?.();
throw new Error(
`Jira cloudId discovery failed: ${res.status} ${res.statusText} on ${url}`,
);
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/adapters/issue-tracker/jira.ts` around lines 42 - 57, The discoverCloudId
method can hang indefinitely; add a fetch timeout using an AbortController
inside discoverCloudId (and a short configurable constant e.g.
CLOUD_ID_TIMEOUT_MS) so the request to `${this.tenantOrigin}/_edge/tenant_info`
is aborted after the timeout; pass the controller.signal into fetch, clear the
timeout on success, handle the abort by throwing a descriptive Error (preserving
the original fetch error) and ensure no resource leaks. Use the existing
function name discoverCloudId to locate the change and keep behavior the same
when fetch succeeds.
| private async rewriteIfTenant(url: string): Promise<string> { | ||
| const parsed = new URL(url); | ||
| if (parsed.origin !== this.tenantOrigin) return url; | ||
| const cloudId = await this.getCloudId(); | ||
| return `${ATLASSIAN_API_ORIGIN}/ex/jira/${cloudId}${parsed.pathname}${parsed.search}`; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the current implementation of rewriteIfTenant and related code
cat -n src/adapters/issue-tracker/jira.ts | head -250 | tail -100Repository: Blazity/ai-workflow
Length of output: 4030
🏁 Script executed:
# Check if there are test files for this adapter
fd -e test -e spec | grep -i jiraRepository: Blazity/ai-workflow
Length of output: 45
🏁 Script executed:
# Search for usage of rewriteIfTenant and getCloudId
rg "rewriteIfTenant|getCloudId|downloadAttachment" src/adapters/issue-tracker/jira.ts -B 2 -A 5Repository: Blazity/ai-workflow
Length of output: 1751
🏁 Script executed:
# Check for any test files that verify URL rewriting behavior
find . -name "*.test.*" -o -name "*.spec.*" | xargs rg "rewriteIfTenant\|Jira.*url\|attachment.*url" 2>/dev/null | head -50Repository: Blazity/ai-workflow
Length of output: 45
🏁 Script executed:
# Search for where attachment URLs are retrieved from API responses
rg "attachment|content.*url" src/adapters/issue-tracker/jira.ts -B 3 -A 3Repository: Blazity/ai-workflow
Length of output: 1530
🏁 Script executed:
# Look for ATLASSIAN_API_ORIGIN and other constants
rg "ATLASSIAN_API_ORIGIN|tenantOrigin" src/adapters/issue-tracker/jira.ts -B 2 -A 2Repository: Blazity/ai-workflow
Length of output: 1709
🏁 Script executed:
# Search entire codebase for where attachment URLs might come from
rg "\.content|attachment.*url|getAttachment" src/ -l | head -20Repository: Blazity/ai-workflow
Length of output: 409
🏁 Script executed:
# Check the full file structure to understand the class better
head -150 src/adapters/issue-tracker/jira.tsRepository: Blazity/ai-workflow
Length of output: 4874
🏁 Script executed:
# Check the test file for Jira to see if there are mocked attachment URLs
cat src/adapters/issue-tracker/jira.test.tsRepository: Blazity/ai-workflow
Length of output: 17136
🏁 Script executed:
# Look for any fixtures or mock data
fd -e fixture -e mock -e json | xargs grep -l "jira\|attachment" 2>/dev/null | head -10Repository: Blazity/ai-workflow
Length of output: 45
🌐 Web query:
Atlassian Jira Cloud REST API v3 attachment URL format scoped token gateway /ex/jira/cloudId
💡 Result:
For Jira Cloud REST API v3 with a scoped token, the attachment download is not done via a tenant URL like https://{site}.atlassian.net/.... Instead, you call the attachment-content REST resource through the Atlassian API gateway base URL: https://api.atlassian.com/ex/jira/{cloudId}/rest/api/3/... [1][2] Attachment “content” endpoint (v3) GET https://api.atlassian.com/ex/jira/{cloudId}/rest/api/3/attachment/content/{id} [3] This returns the bytes of the attachment (optionally with Range for partial downloads). [3] So, the “URL format” you’re asking about is: https://api.atlassian.com/ex/jira/{cloudId}/rest/api/3/attachment/content/{id} If your question is specifically about the older attachment “direct file” URLs returned inside attachment metadata (often a relative /secure/... link), those URLs may not work the same way with scoped tokens; Atlassian’s guidance generally is to use the gateway URL for scoped API tokens. [1][2] Example (template) curl -sS \ -H "Authorization: Bearer $SCOPED_TOKEN" \ "https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/attachment/content/$ATTACHMENT_ID" --output file.bin
Citations:
- 1: https://community.atlassian.com/forums/Jira-questions/How-to-use-a-Scoped-API-key-for-user-query/qaq-p/3114697
- 2: https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/
- 3: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-attachments/
🌐 Web query:
Jira Cloud API attachment content URL path /jira/rest/api/3 vs /rest/api/3
💡 Result:
In Jira Cloud, the official REST API v3 endpoints use the path /rest/api/3. For example, to get attachment content, use GET /rest/api/3/attachment/content/{id} [1][2]. However, when you retrieve attachment metadata via GET /rest/api/3/attachment/{id} or from an issue's fields, the returned "content" URL field points to /jira/rest/api/3/attachment/content/{id}, such as https://your-domain.atlassian.net/jira/rest/api/3/attachment/content/10000 [1]. Both paths are functional for accessing attachment content: /rest/api/3 serves the API directly, while /jira/rest/api/3 is an equivalent legacy or web-context path that redirects or serves the same content [1][3]. Use /rest/api/3 for API calls and the provided "content" URL (which may use /jira/rest/api/3) to download files, as it handles authentication properly [4].
Citations:
- 1: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-attachments/
- 2: https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/
- 3: https://community.atlassian.com/forums/Jira-questions/How-to-get-attachment-content-using-REST-API-version-3/qaq-p/2085747
- 4: https://confluence.atlassian.com/jirakb/exporting-all-attachments-from-a-single-project-in-jira-cloud-using-the-rest-api-1426424536.html
🏁 Script executed:
# Search for any references to /jira/rest in the codebase
rg "/jira/rest" --type typescript --type jsonRepository: Blazity/ai-workflow
Length of output: 98
🏁 Script executed:
# Check if there are integration tests or fixtures that show attachment URLs
rg "attachment.*content.*http|content.*url" src/ --type typescript -A 2 -B 2Repository: Blazity/ai-workflow
Length of output: 98
🏁 Script executed:
# Search for any references to /jira/rest in the codebase
rg "/jira/rest" -t ts -t jsRepository: Blazity/ai-workflow
Length of output: 45
🏁 Script executed:
# Check if there are integration tests or fixtures that show attachment URLs
rg "attachment.*content.*http|content.*url" src/ -t ts -t js -A 2 -B 2Repository: Blazity/ai-workflow
Length of output: 1591
Normalize tenant attachment paths before rewriting them through the Atlassian gateway.
Scoped Jira token calls are documented against /ex/jira/<cloudId>/rest/api/3/..., but attachment metadata can return tenant URLs under /jira/rest/api/3/.... Direct concatenation preserves that extra /jira prefix, so attachment URLs become /ex/jira/<cloudId>/jira/rest/api/3/... and fail at download time. (developer.atlassian.com)
Suggested fix
private async rewriteIfTenant(url: string): Promise<string> {
const parsed = new URL(url);
if (parsed.origin !== this.tenantOrigin) return url;
+ const apiPath = parsed.pathname.startsWith("/jira/rest/")
+ ? parsed.pathname.slice("/jira".length)
+ : parsed.pathname;
const cloudId = await this.getCloudId();
- return `${ATLASSIAN_API_ORIGIN}/ex/jira/${cloudId}${parsed.pathname}${parsed.search}`;
+ return `${ATLASSIAN_API_ORIGIN}/ex/jira/${cloudId}${apiPath}${parsed.search}`;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private async rewriteIfTenant(url: string): Promise<string> { | |
| const parsed = new URL(url); | |
| if (parsed.origin !== this.tenantOrigin) return url; | |
| const cloudId = await this.getCloudId(); | |
| return `${ATLASSIAN_API_ORIGIN}/ex/jira/${cloudId}${parsed.pathname}${parsed.search}`; | |
| private async rewriteIfTenant(url: string): Promise<string> { | |
| const parsed = new URL(url); | |
| if (parsed.origin !== this.tenantOrigin) return url; | |
| const apiPath = parsed.pathname.startsWith("/jira/rest/") | |
| ? parsed.pathname.slice("/jira".length) | |
| : parsed.pathname; | |
| const cloudId = await this.getCloudId(); | |
| return `${ATLASSIAN_API_ORIGIN}/ex/jira/${cloudId}${apiPath}${parsed.search}`; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/adapters/issue-tracker/jira.ts` around lines 206 - 210, The
rewriteIfTenant method incorrectly preserves a leading "/jira" path segment from
tenant attachment URLs, causing duplicated "/jira" when building the gateway
URL; update rewriteIfTenant (alongside ATLASSIAN_API_ORIGIN and getCloudId
usage) to normalize parsed.pathname by stripping a leading "/jira" segment if
present (e.g. remove a leading "/jira" only once before concatenation) and then
return
`${ATLASSIAN_API_ORIGIN}/ex/jira/${cloudId}${normalizedPath}${parsed.search}` so
attachment URLs become `/ex/jira/<cloudId>/rest/...` instead of
`/ex/jira/<cloudId>/jira/rest/...`.
Summary by CodeRabbit
New Features
Documentation
Configuration
JIRA_EMAILas a required environment variable.