Skip to content

Build1 publisher3 min readPublished

Forge lint drops every Jira call whose endpoint arrives in a variable

On @forge/cli 13.4.0 the linter runs inside forge deploy and blocks it, so a clean run looks like a passed check. One request helper is enough to leave a missing scope to surface as a 401 in production.

The Engineer · Build desk

Illustration accompanying Forge lint drops every Jira call whose endpoint arrives in a variable

What happened

  • On @forge/cli 13.4.0 forge deploy runs the linter first and refuses to ship the app when the linter finds errors, with forge deploy --no-verify as the documented way past it.
  • A call whose scope is undeclared comes back as HTTP 401 carrying x-failure-category: FAILURE_CLIENT_SCOPE_CHECK and a body saying scope does not match, not as a 403.
  • The Atlassian page that documents the scope behaviour describes the linter as assistance, shows a Confluence scope error as its example, and was last updated on 30 October 2024.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • exposure An app can clear the deploy gate and fail at a customer's site, because the scope check happens at request time in Atlassian's gateway and whoever reads the log has to inspect the x-failure-category header to separate a missing scope from the other causes of a 401.
  • cost Trusting --fix can put a scope in the manifest that the app never calls, and the installing admin is the one who gets a major version bump and a fresh consent prompt.
  • contradiction Atlassian's two pages point different ways: the CLI reference documents lint without mentioning scopes at all, while the page that does document the scope check calls the command assistance and predates the release in which it became a deploy gate.
  • decision Teams with a shared request helper now choose between inlining endpoint strings at every call site so the parser can see them and maintaining their own scope inventory alongside the manifest.

The linter resolves an endpoint only when the string sits at the call site. A literal, a template literal and a route`` tag all work; a path that arrives as a variable is dropped, and no missing scope is reported [1]. One request helper is enough to cover every Atlassian REST call in a codebase, and the dev.to author reports linting an app that needed `write:jira-work` and `read:jira-user`, with neither declared, and getting `No issues found.` [2]

The check that does hold is on Atlassian's side. A call whose scope is not declared comes back as HTTP 401 with the header `x-failure-category: FAILURE_CLIENT_SCOPE_CHECK` and the body `{"code":401,"message":"Unauthorized; scope does not match"}`, not as a 403 [4]. A 401 also arrives for a dozen unrelated reasons, so the header is the part that identifies the cause [17].

`--fix` fails in the other direction. When it cannot read the HTTP method it defaults the call to GET, so it can add a scope the app does not need [5]. Adding any scope forces a major version and admin re-consent [6].

Getting this wrong in production looks like the thread the post cites. A developer posted on the Atlassian developer community that every Assets API call from their Forge app returned `{"code":401,"message":"Unauthorized"}` even with the CMDB scopes declared in `manifest.yml` and `forge install --upgrade` run, and that adding those scopes broke endpoints that had been returning 200. Someone from Atlassian asked for more detail, the poster explained that their case needs app context instead of user context, and the thread ends with no accepted answer [7].

Atlassian never promised the check was exhaustive. The scopes page says "You can use the forge lint command to assist you with adding missing scopes in your app" [8], and after the example error it tells readers to "continue investigating outstanding errors" [12]. That page was last updated on 30 October 2024 [13], roughly 21 months before the author's test [1]. The CLI reference for lint, updated 6 July 2026, says the command will "check the source files for common errors", and the words permission and scope do not appear on it [11]. The deploy-time behaviour came later: Atlassian's changelog puts the approval workflow at @forge/cli 13.3.0 [14], and on 13.4.0 the run stops with "The deploy failed due to errors in the app code" [15].

For the gate to mean what a clean run implies, every Jira and Confluence endpoint in the tree has to be written at the point of call. Codebases that route calls through a shared helper get the least from it. Most codebases that have been maintained for a while route calls that way. The measurement is one app on one Jira site, run on 17 August 2026 against @forge/cli 13.4.0 and @forge/lint 6.2.0, both published earlier that day [10]. Checking it on a given tree costs one edit: a single inlined path, a rerun of the linter, and a comparison of the two reports. `forge deploy --no-verify` still skips the linter entirely [16].

What to watch

  • Whether a later @forge/lint release resolves endpoint paths through variables or helper functions.
  • Whether Atlassian revises the CLI lint reference, or the scopes page last touched on 30 October 2024, to state which call shapes the parser cannot read.
  • Whether the Assets API thread gets an answer on declaring CMDB scopes for app context.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories