Skip to content

fix(cli): linear app-template prints the consent page as Linear's Redirect URI — print the AgentCore callback instead (#914) - #918

Open
isadeks wants to merge 1 commit into
mainfrom
fix/914-linear-template-redirect-uri
Open

isadeks wants to merge 1 commit into
mainfrom
fix/914-linear-template-redirect-uri

Conversation

@isadeks

@isadeks isadeks commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Closes #914.

The bug

bgagent linear app-template printed the hosted consent page as the Linear Redirect URI. Linear never redirects there — it is AgentCore's resourceOauth2ReturnUrl (cli/src/linear-vault.ts:399), where AgentCore sends the browser after Linear redirects, to show the session id setup asks for.

So an operator who registered exactly what the template printed still failed consent:

Invalid redirect_uri parameter for the application

and the template's own traps list sent them looking for a trailing-slash typo. Hit live on a three-workspace dev stack; the field had to be assembled by hand from the authorize URL to get consent through.

Linear redirects to the AgentCore provider callback on the setup path, and to the CLI's loopback listener on the older add-workspace path. Previously the code swapped one for the other (opts.hostedConsentUrl ? [hostedConsentUrl] : [callbackUrl]), so with the vault deployed the field held only a URI Linear ignores — and the URI it needs appeared solely if --slug happened to resolve an already-created provider.

What the field holds now

One line, and only what the operator's own path uses:

State Redirect URIs
Vault, provider exists the AgentCore callback, alone
Vault, first run Leave empty — bgagent linear setup <slug> prints the URI to add, then stops.
No vault the loopback listener, with the limitation and the flag that fixes it

A first run genuinely cannot show the callback: AgentCore mints the id during CreateOauth2CredentialProvider, which needs the Client ID/Secret from an app that does not exist yet. So the template says so in one line and hands off to setup, rather than filling the field with another flow's URI.

The consent page is still printed — it is how an operator confirms the vault is deployed — but after the form and labelled nothing to paste, so it cannot be mistaken for a field value.

setup now prints the callback on every consent

provider.created was standing in for "this URI is registered on the Linear app". It is true exactly once per provider lifetime, while the real condition stays false until a human pastes the URI in. Anyone who interrupted the first run, lost the scrollback, or never finished the paste got no remedy on any later run — just an authorize URL guaranteed to die in the browser. The code comment said as much: "the redirect URIs were dealt with on the first run above."

Tests

995 passing, 64 suites. Four existing tests asserted the old model — one named "the hosted URI REPLACES the loopback rather than joining it" — and are corrected rather than deleted, since each records which mistake was being prevented. New coverage:

  • the consent page never appears in the Redirect URIs block, while still appearing in the output
  • only the AgentCore callback is listed once the provider exists
  • the deferred state is exactly two lines, so the explanation cannot grow back
  • every URI sits alone on its line (an annotated ~100-char callback would wrap, producing the two-malformed-entries failure the traps warn about)

Verified live

Against a real vault stack (ComputeSubstrate=agentcore, LinearVaultConsentUrl present) and a real Linear workspace: --slug <workspace> renders that workspace's own callback, and a bare run defers to setup.

Scope notes

`bgagent linear app-template` printed the hosted consent page as the Linear
Redirect URI. Linear never redirects there — it is AgentCore's
`resourceOauth2ReturnUrl`, where AgentCore sends the browser AFTER Linear
redirects, to show the session id. So an operator who registered exactly what
the template printed still failed consent with "Invalid redirect_uri parameter
for the application", and the template's own traps list blamed a trailing slash.

Linear redirects to the AgentCore provider callback on the `setup` path and to
the CLI's loopback listener on `add-workspace`. The field now holds only that,
one line per state:

  - provider exists  -> the AgentCore callback, alone
  - vault, first run -> "Leave empty — `setup <slug>` prints the URI to add"
  - no vault         -> the loopback listener

The consent page is still printed, after the form and labelled "nothing to
paste", because it is how an operator confirms the vault is deployed.

`setup` now prints the callback on EVERY consent, not only the run that created
the provider. `provider.created` was standing in for "this URI is registered on
the Linear app": true once per provider lifetime, while the real condition stays
false until a human pastes it in. Anyone who interrupted the first run, or lost
the scrollback, got no remedy on any later run — just an authorize URL that dies
in the browser.

Four existing tests asserted the old model, one named "the hosted URI REPLACES
the loopback rather than joining it". They are corrected rather than deleted,
since they record which mistake was being prevented. A new test pins the
deferred state at exactly two lines so the explanation cannot grow back.

Verified against a live vault stack and a real Linear workspace: the field now
renders the same two-URI list that had to be assembled by hand to get consent
through. 995 CLI tests, 64 suites.

Refs #914
@isadeks
isadeks requested review from a team and backgroundagents as code owners September 22, 2026 22:46

This branch has not been deployed

No deployments
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.

fix(cli): linear app-template prints the consent page as Linear's Redirect URI — Linear needs the AgentCore callback

1 participant