fix(jira-poll): degrade gracefully when project role lookup returns 401 - #6020
Conversation
Jira Cloud returns 401 ("You cannot edit the configuration of this
project.") when the service account lacks Administer Projects permission,
even on a read-only GET /project/{key}/role call. Previously this aborted
the entire poll cycle, making --jira-project unusable without project
admin access. Now the poller logs a warning and continues with external
roles, matching the existing behavior when --jira-project is omitted.
Transient errors (5xx) still fail the cycle so checkpoints are not
advanced past lost events.
Also maps HTTP 401 to forge.ErrForbidden in the Jira APIError.Unwrap,
since Jira Cloud uses 401 for project-level permission denials in
addition to the standard 403.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
E2E tests did not runE2E tests run automatically for org/repo members and collaborators on pull requests. For other contributors, a maintainer must add the See E2E testing guide for details. |
|
Thank you for your interest in contributing to fullsend, @samanthajayasinghe. This project uses a vouch system for first-time contributors. Before submitting a pull request, you need to be vouched by a maintainer. To get vouched:
See CONTRIBUTING.md for details. |
PR Summary by QodoGracefully degrade Jira poller when project role lookup returns 401/403
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo
1. 401 mapped too broadly
|
| TargetRepo: "acme/platform", | ||
| JiraBaseURL: "https://acme.atlassian.net", | ||
| JiraProject: "PROJ", | ||
| OutputPath: outputPath, |
There was a problem hiding this comment.
1. Org-specific jira url in test 📘 Rule violation ⛨ Security
The new test hardcodes JiraBaseURL as https://acme.atlassian.net and TargetRepo as acme/platform, which are environment/organization-specific identifiers. This can leak real tenancy/repo naming and violates the requirement to avoid hardcoded sensitive environment-specific identifiers in source code/tests.
Agent Prompt
## Issue description
A new test hardcodes organization/environment-specific identifiers (`https://acme.atlassian.net`, `acme/platform`). The compliance rule requires avoiding hardcoded sensitive environment-specific identifiers; tests should use clearly fake placeholders.
## Issue Context
This is test code, so using placeholder values (e.g., `https://example.atlassian.net` and `example/repo`) keeps the test realistic without embedding org-specific identifiers.
## Fix Focus Areas
- internal/jirapoll/poller_test.go[578-581]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| // Jira Cloud returns 401 for project-level permission denials | ||
| // (e.g. project role listing) in addition to the standard 403. | ||
| if e.StatusCode == http.StatusForbidden || e.StatusCode == http.StatusUnauthorized { | ||
| return forge.ErrForbidden |
There was a problem hiding this comment.
2. 401 mapped too broadly 🐞 Bug ≡ Correctness
jira.APIError.Unwrap() now maps every HTTP 401 to forge.ErrForbidden, conflating authentication/configuration failures with authorization denials. Callers that treat ErrForbidden as non-fatal (e.g., role membership load and status transition resolution) may proceed and advance checkpoints / drop events instead of failing fast on bad auth.
Agent Prompt
### Issue description
`APIError.Unwrap()` currently maps HTTP 401 to `forge.ErrForbidden` for all Jira endpoints. This is too broad: 401 can represent invalid credentials/wrong auth scheme, which should not be treated as a mere permission denial because downstream code may intentionally degrade/skip on `ErrForbidden`.
### Issue Context
The poller now explicitly degrades on `errors.Is(err, forge.ErrForbidden)` when loading project role membership; additionally, other jira-poll logic drops some events on `ErrForbidden`.
### Fix approach (recommended)
- Revert `APIError.Unwrap()` to map **only** HTTP 403 -> `forge.ErrForbidden` (keep 404 -> `forge.ErrNotFound`).
- Handle Jira Cloud’s project-role-specific 401 quirk **at the call site**:
- In `LiveClient.GetProjectRoleMembership`, detect an `*jira.APIError` with `StatusCode==401` (and optionally a known permission-denial message) and wrap it with `forge.ErrForbidden` (e.g., `return nil, fmt.Errorf("%w: %w", forge.ErrForbidden, err)`), so only that endpoint participates in the graceful-degradation path.
- Update/add tests:
- Add a test that `GetProjectRoleMembership` 401 maps to `forge.ErrForbidden`.
- Update `TestErrorResponse_401` to assert 401 is **not** `forge.ErrForbidden` for generic endpoints like `/myself` (or rename/split the test to reflect the endpoint-specific behavior).
### Fix Focus Areas
- internal/forge/jira/client.go[158-167]
- internal/forge/jira/client.go[543-548]
- internal/forge/jira/client_test.go[448-465]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Jira Cloud returns 401 ("You cannot edit the configuration of this project.") when the service account lacks Administer Projects permission, even on a read-only GET /project/{key}/role call. Previously this aborted the entire poll cycle, making --jira-project unusable without project admin access. Now the poller logs a warning and continues with external roles, matching the existing behavior when --jira-project is omitted. Transient errors (5xx) still fail the cycle so checkpoints are not advanced past lost events.
Also maps HTTP 401 to forge.ErrForbidden in the Jira APIError.Unwrap, since Jira Cloud uses 401 for project-level permission denials in addition to the standard 403.
Summary
Related Issue
Changes
Testing
make lintpasses (stage changes first, then run)Checklist
!for breaking changes)