Skip to content

Dev#78

Merged
woywro merged 3 commits into
mainfrom
dev
May 11, 2026
Merged

Dev#78
woywro merged 3 commits into
mainfrom
dev

Conversation

@woywro
Copy link
Copy Markdown
Collaborator

@woywro woywro commented May 11, 2026

Summary by CodeRabbit

  • New Features

    • Updated Jira authentication to use service-account Bearer tokens through Atlassian Cloud API, replacing email-based authentication.
  • Documentation

    • Clarified Jira setup requirements with service-account scoped credentials and Bearer token configuration.
    • Updated environment variable examples and setup guides to reflect new authentication method.
  • Configuration

    • Removed JIRA_EMAIL as a required environment variable.
    • Updated example files and CI/CD workflows accordingly.

Review Change Stack

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ai-workflow-app Ready Ready Preview, Comment May 11, 2026 10:49am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

📝 Walkthrough

Walkthrough

This 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 api.atlassian.com origin. All related environment schemas, tests, wiring, E2E helpers, and documentation are updated to remove JIRA_EMAIL and reflect the new auth model.

Changes

Jira Bearer Token and CloudId Migration

Layer / File(s) Summary
Environment Schema Updates
env.ts, e2e/env.ts
Remove JIRA_EMAIL field from Zod validation schemas; Env and E2EEnv types no longer include email credentials.
Jira Adapter Configuration
src/adapters/issue-tracker/jira.ts
JiraConfig removes email field, adds optional cloudId; JiraAdapter constructor initializes Bearer token auth, caches tenantOrigin, and prepares cloudIdPromise for API routing.
CloudId Discovery and API Routing
src/adapters/issue-tracker/jira.ts
Implement getCloudId(), discoverCloudId() to fetch cloudId via tenant-info endpoint; introduce apiUrl(path) helper to route requests through api.atlassian.com/ex/jira/{cloudId}; add rewriteIfTenant() to map tenant URLs and update buildAttachmentHeaders() to apply Bearer auth only for Atlassian API origin.
Jira Adapter Core Methods
src/adapters/issue-tracker/jira.ts
Update comment URL generation to use tenantOrigin; refactor attachment download to rewrite contentUrl and redirect Location headers through cloudId-based Atlassian gateway before Bearer-authenticated fetch.
Adapter Unit Tests
src/adapters/issue-tracker/jira.test.ts
Add CLOUD_ID and API_BASE test constants; update test helper to use cloudId instead of email; revise redirect assertions to expect Bearer Authorization header and validate Atlassian gateway URL rewriting.
Adapter Factory Wiring
src/lib/adapters.ts, src/lib/step-adapters.ts
Remove email: env.JIRA_EMAIL from JiraAdapter initialization in both createAdapters() and createStepAdapters().
E2E Jira Helpers and Tests
e2e/helpers/jira.ts, e2e/tier2/us02-attachments.test.ts
E2E helpers perform cloudId discovery with Bearer token caching; route all requests via Atlassian API origin; rewrite attachment URLs and downloads to use Bearer auth. Update E2E attachment test to remove email parameter.
Documentation, Config, and Workflows
.claude/skills/init-env/SKILL.md, .claude/skills/init-jira/SKILL.md, .claude/skills/init-jira/references/*, .env.example, .env.e2e.example, .github/workflows/ci.yml, .github/workflows/e2e.yml, SETUP.md
Update all setup documentation and guidance to describe Atlassian service-account Bearer tokens via api.atlassian.com, cloudId discovery, and credential scopes (read:jira-work, write:jira-work); remove all references to JIRA_EMAIL from examples, prompts, workflows, and configuration tables.
Test Fixtures
env.test.ts
Remove JIRA_EMAIL from VALID_ENV test fixture to align with new schema.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Blazity/ai-workflow#70: Modifies Jira adapter config and authentication handling (both refactor auth/config in src/adapters/issue-tracker/jira.ts).
  • Blazity/ai-workflow#61: Updates Jira adapter return types and adapter wiring (both touch src/adapters/issue-tracker/jira.ts and adapter factories).
  • Blazity/ai-workflow#52: Modifies Jira issue-tracker adapter and related tests (both refactor adapter behavior and test coverage).

Poem

🐰 A rabbit hops through Atlassian gates,
Bearer tokens now authenticate with grace,
CloudIds dance where emails once did roam,
API gateways guard the tenant space,
From basic auth to Bearer's gleaming face! 🎫✨

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Dev' is vague and generic, providing no meaningful information about the changeset's actual content or objectives. Rename the title to reflect the main change, such as 'Remove JIRA_EMAIL authentication and migrate to Bearer token-based API calls' or similar.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9e2a1ca and a64e02d.

📒 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.yml
  • SETUP.md
  • e2e/env.ts
  • e2e/helpers/jira.ts
  • e2e/tier2/us02-attachments.test.ts
  • env.test.ts
  • env.ts
  • src/adapters/issue-tracker/jira.test.ts
  • src/adapters/issue-tracker/jira.ts
  • src/lib/adapters.ts
  • src/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

Comment on lines +42 to +57
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;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Comment on lines +206 to +210
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}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 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 -100

Repository: 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 jira

Repository: 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 5

Repository: 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 -50

Repository: 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 3

Repository: 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 2

Repository: 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 -20

Repository: 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.ts

Repository: 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.ts

Repository: 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 -10

Repository: 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:


🌐 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:


🏁 Script executed:

# Search for any references to /jira/rest in the codebase
rg "/jira/rest" --type typescript --type json

Repository: 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 2

Repository: 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 js

Repository: 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 2

Repository: 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.

Suggested change
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/...`.

@woywro woywro merged commit 92a887c into main May 11, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant