Add Launchpad OAuth 2 fallback support#6
Merged
Conversation
When BC3 OAuth 2.1 discovery fails (no .well-known endpoint), fall back to Launchpad OAuth 2 for authentication. This enables bcq to work with Basecamp instances that don't yet support OAuth 2.1. Key differences between providers: - BC3: Uses DCR, PKCE (S256), response_type=code, grant_type params - Launchpad: Pre-registered clients, no PKCE, type=web_server params The oauth_type is stored in credentials to ensure proper refresh handling. Launchpad client credentials can be set via BCQ_CLIENT_ID/BCQ_CLIENT_SECRET environment variables or oauth_client_id/oauth_client_secret in config. Warns when --scope read is used with Launchpad (which only supports full access) and auto-overrides to full scope.
Token refresh now uses the stored oauth_type to select the correct endpoint and request format: - Launchpad: Uses BCQ_LAUNCHPAD_TOKEN_URL with type=refresh param - BC3: Uses discovered token endpoint with grant_type=refresh_token This ensures refresh works correctly regardless of which OAuth provider was used for initial authentication. The endpoint is selected based on stored credentials, not current discovery results, preventing issues if discovery availability changes between login and refresh.
Tests for: - auth status displays correct provider (BC3 vs Launchpad) - JSON output includes oauth_provider field - _load_launchpad_client loads from env vars and config - _get_oauth_type defaults to launchpad when discovery fails - refresh_token uses correct endpoint for each provider - refresh_token uses correct param format (type=refresh vs grant_type) - refresh_token preserves oauth_type in credentials
Update bcq auth help to explain: - Two OAuth providers (BC3 OAuth 2.1 and Launchpad OAuth 2) - Launchpad always grants full access (--scope read is ignored) - How to configure Launchpad client credentials
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d856cb8958
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Address PR review feedback: BC3 refresh was calling _token_endpoint() which could fall back to Launchpad if discovery fails. This could cause BC3 tokens to be refreshed against the wrong endpoint. Fix by storing the token_endpoint in credentials during initial auth, then preferring the stored endpoint during refresh. Legacy credentials without stored endpoint fall back to discovery (BC3) or env var (Launchpad). Tests verify: - Stored token_endpoint takes precedence over env vars - Legacy credentials without stored endpoint still work
jeremy
added a commit
that referenced
this pull request
Feb 5, 2026
* Clarify cross-project query limitations in skill docs - Add invariant #6: project scope is mandatory for resource queries - Add note above Quick Reference about cross-project limitations - Fix "My todos" example to include required --in flag - Add "All todos (cross-project)" example using recordings - Update decision tree to emphasize recordings as ONLY cross-project option Addresses #118 * Document --assignee limitation and --card-table requirement - Clarify --assignee flag only works on todos, not cards/messages - Add note that cards don't support --assignee filtering - Document --card-table requirement for projects with multiple card tables - Add example: bcq cards --card-table <id> --in <project> - Update columns comment to note --card-table may be needed Addresses #119 * Document card completion detection and recordings status filtering - Add card completion detection: parent.type "Kanban::DoneColumn" and completed: true - Document limitation: Basecamp doesn't track card move timestamps - Add --status filtering note: recordings default to active status - Add examples for --status archived and --all flags - Note that --limit is not supported on timeline commands - Add decision tree note about status filtering for archived items Addresses #120 * Revert output pipeline changes, fix project scope docs Restore json.Number handling in output pipeline to avoid scientific notation regression with --ids-only. Update SKILL.md to clarify that project scope can come from --in <project> or .basecamp/config.json. --------- Co-authored-by: Jeremy Daer <[email protected]>
jeremy
added a commit
that referenced
this pull request
Feb 19, 2026
* Add Launchpad OAuth 2 fallback support When BC3 OAuth 2.1 discovery fails (no .well-known endpoint), fall back to Launchpad OAuth 2 for authentication. This enables bcq to work with Basecamp instances that don't yet support OAuth 2.1. Key differences between providers: - BC3: Uses DCR, PKCE (S256), response_type=code, grant_type params - Launchpad: Pre-registered clients, no PKCE, type=web_server params The oauth_type is stored in credentials to ensure proper refresh handling. Launchpad client credentials can be set via BCQ_CLIENT_ID/BCQ_CLIENT_SECRET environment variables or oauth_client_id/oauth_client_secret in config. Warns when --scope read is used with Launchpad (which only supports full access) and auto-overrides to full scope. * Add provider-aware token refresh Token refresh now uses the stored oauth_type to select the correct endpoint and request format: - Launchpad: Uses BCQ_LAUNCHPAD_TOKEN_URL with type=refresh param - BC3: Uses discovered token endpoint with grant_type=refresh_token This ensures refresh works correctly regardless of which OAuth provider was used for initial authentication. The endpoint is selected based on stored credentials, not current discovery results, preventing issues if discovery availability changes between login and refresh. * Add OAuth provider and token refresh tests Tests for: - auth status displays correct provider (BC3 vs Launchpad) - JSON output includes oauth_provider field - _load_launchpad_client loads from env vars and config - _get_oauth_type defaults to launchpad when discovery fails - refresh_token uses correct endpoint for each provider - refresh_token uses correct param format (type=refresh vs grant_type) - refresh_token preserves oauth_type in credentials * Document Launchpad OAuth scope limitations Update bcq auth help to explain: - Two OAuth providers (BC3 OAuth 2.1 and Launchpad OAuth 2) - Launchpad always grants full access (--scope read is ignored) - How to configure Launchpad client credentials * Store token_endpoint in credentials for refresh stability Address PR review feedback: BC3 refresh was calling _token_endpoint() which could fall back to Launchpad if discovery fails. This could cause BC3 tokens to be refreshed against the wrong endpoint. Fix by storing the token_endpoint in credentials during initial auth, then preferring the stored endpoint during refresh. Legacy credentials without stored endpoint fall back to discovery (BC3) or env var (Launchpad). Tests verify: - Stored token_endpoint takes precedence over env vars - Legacy credentials without stored endpoint still work
jeremy
added a commit
that referenced
this pull request
Feb 19, 2026
* Clarify cross-project query limitations in skill docs - Add invariant #6: project scope is mandatory for resource queries - Add note above Quick Reference about cross-project limitations - Fix "My todos" example to include required --in flag - Add "All todos (cross-project)" example using recordings - Update decision tree to emphasize recordings as ONLY cross-project option Addresses #118 * Document --assignee limitation and --card-table requirement - Clarify --assignee flag only works on todos, not cards/messages - Add note that cards don't support --assignee filtering - Document --card-table requirement for projects with multiple card tables - Add example: bcq cards --card-table <id> --in <project> - Update columns comment to note --card-table may be needed Addresses #119 * Document card completion detection and recordings status filtering - Add card completion detection: parent.type "Kanban::DoneColumn" and completed: true - Document limitation: Basecamp doesn't track card move timestamps - Add --status filtering note: recordings default to active status - Add examples for --status archived and --all flags - Note that --limit is not supported on timeline commands - Add decision tree note about status filtering for archived items Addresses #120 * Revert output pipeline changes, fix project scope docs Restore json.Number handling in output pipeline to avoid scientific notation regression with --ids-only. Update SKILL.md to clarify that project scope can come from --in <project> or .basecamp/config.json. --------- Co-authored-by: Jeremy Daer <[email protected]>
jeremy
added a commit
that referenced
this pull request
Mar 5, 2026
Agents were routing "my todos" queries to `recordings todos` which returns all todos with no assignee data, making per-person filtering impossible. Five edits steer agents toward `reports assigned` instead: - Invariant #6: reword to list cross-project exceptions coherently - Quick reference note: split guidance for assigned work vs type browsing - Quick reference table: add "My todos (cross-project)" row, annotate recordings row with "no assignee data" warning - Decision tree: add "My assigned work?" branch before type browsing, note that recordings cannot filter by person - Recordings section header: prefer reports assigned for assigned todos
jeremy
added a commit
that referenced
this pull request
Mar 5, 2026
* Add hidden --assignee flag to recordings with context-aware redirect recordings lacks assignee data, so --assignee cannot work. Rather than silently returning unfiltered results, a hidden flag intercepts the request and redirects to the correct command: - With --in/--project: suggests `basecamp todos --assignee <person> --in <project>` - Without project scope: suggests `basecamp reports assigned [person]` The guard runs in PreRunE (root) or early RunE (list subcommand) so it fires before account validation. Hint arguments are %q-quoted for shell safety, and "me" matching is case-insensitive. Includes unit tests (11) and e2e tests (4) covering positional args, --type flag form, --in conditional path, non-"me" assignee preservation, case-insensitive "me" handling, and hidden flag assertions. * Fix SKILL.md agent routing for "my todos" queries Agents were routing "my todos" queries to `recordings todos` which returns all todos with no assignee data, making per-person filtering impossible. Five edits steer agents toward `reports assigned` instead: - Invariant #6: reword to list cross-project exceptions coherently - Quick reference note: split guidance for assigned work vs type browsing - Quick reference table: add "My todos (cross-project)" row, annotate recordings row with "no assignee data" warning - Decision tree: add "My assigned work?" branch before type browsing, note that recordings cannot filter by person - Recordings section header: prefer reports assigned for assigned todos
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
oauth_typein credentials for proper token refresh handling--scope readis used with Launchpad (only supports full access)OAuth Provider Comparison
.well-known/oauth-authorization-serverresponse_type=codetype=web_servergrant_type=authorization_codetype=web_servergrant_type=refresh_tokentype=refreshTest plan
--scope readwarning appears for Launchpadoauth_type