Conversation
During early provisioning, computeId isn't set yet. The provider's getStatus would return 'error' immediately. Now we check the DB status first and return 'provisioning' if that's the current state. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The /wait endpoint blocks until the machine reaches the target state, much more efficient than polling every 3 seconds. Also increased timeout to 120s since machines can take longer to start. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resolved trajectory file conflicts by merging both branches' entries. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR implements GitHub repository access management with Nango-based permission checking and enhances the Codex authentication flow with a CLI helper utility. The changes enable users to view and manage GitHub repositories they have access to through OAuth, and provide a streamlined CLI-based method for capturing OAuth callbacks during Codex authentication.
Key changes:
- Added GitHub repository permission APIs using Nango OAuth proxy for access control
- Implemented
RepoAccessPanelcomponent for managing repository access in workspace settings - Enhanced Codex authentication with CLI helper that captures OAuth callbacks locally
- Refactored machine startup process to use Fly.io's
/waitendpoint for more efficient state polling
Reviewed changes
Copilot reviewed 18 out of 20 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/dashboard/react-components/settings/WorkspaceSettingsPanel.tsx | Added "GitHub Access" section with navigation and RepoAccessPanel integration |
| src/dashboard/react-components/RepoAccessPanel.tsx | New component for displaying and managing GitHub repository access with permission levels |
| src/dashboard/react-components/ProviderAuthFlow.tsx | Enhanced Codex auth flow with CLI helper option and automatic callback polling |
| src/cloud/services/nango.ts | Added methods for checking user repository access and listing accessible repos via Nango |
| src/cloud/server.ts | Registered codex-auth-helper router and exempted callback endpoint from CSRF |
| src/cloud/provisioner/index.ts | Refactored machine startup to use Fly.io's blocking /wait endpoint |
| src/cloud/api/repos.ts | Added three new endpoints for repository access checking via Nango OAuth |
| src/cloud/api/codex-auth-helper.ts | New API for CLI-based OAuth callback capture with session management |
| src/cli/index.ts | Added codex-auth command for local OAuth callback capture |
| package.json | Version bump to 1.2.2 and agent-trajectories dependency update |
| .github/workflows/publish.yml | Comprehensive workflow redesign with manual version control and dry-run support |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } catch (err) { | ||
| console.error('Failed to fetch CLI session:', err); | ||
| } | ||
| }, [isCodexFlow, csrfToken, startCliPolling]); |
There was a problem hiding this comment.
The startCliPolling dependency in the useCallback creates a circular dependency since startCliPolling itself is defined as a useCallback. This can lead to unnecessary re-renders or stale closures. Consider removing startCliPolling from the dependencies array since it's a stable callback reference.
| }, [isCodexFlow, csrfToken, startCliPolling]); | |
| }, [isCodexFlow, csrfToken]); |
| setInterval(() => { | ||
| const now = Date.now(); | ||
| for (const [id, session] of pendingAuthSessions) { | ||
| // Remove sessions older than 10 minutes | ||
| if (now - session.createdAt.getTime() > 10 * 60 * 1000) { | ||
| pendingAuthSessions.delete(id); | ||
| } | ||
| } | ||
| }, 60000); |
There was a problem hiding this comment.
The cleanup interval is created at module load time and will never be cleared, even if the server shuts down. This can cause memory leaks or unexpected behavior. Store the interval ID and provide a cleanup mechanism, or integrate this into the server lifecycle.
| workspaces={workspace ? [{ | ||
| id: workspace.id, | ||
| name: workspace.name, | ||
| repositoryFullName: workspace.repositories[0]?.fullName, | ||
| status: workspace.status as 'provisioning' | 'running' | 'stopped' | 'error', | ||
| }] : []} |
There was a problem hiding this comment.
Accessing workspace.repositories[0] without checking if the array exists or has elements could cause runtime errors. While the optional chaining handles undefined, if repositories is an empty array, this will pass undefined to RepoAccessPanel, which may not handle it correctly.
| workspaces={workspace ? [{ | |
| id: workspace.id, | |
| name: workspace.name, | |
| repositoryFullName: workspace.repositories[0]?.fullName, | |
| status: workspace.status as 'provisioning' | 'running' | 'stopped' | 'error', | |
| }] : []} | |
| workspaces={ | |
| workspace && Array.isArray(workspace.repositories) && workspace.repositories.length > 0 | |
| ? [{ | |
| id: workspace.id, | |
| name: workspace.name, | |
| repositoryFullName: workspace.repositories[0].fullName, | |
| status: workspace.status as 'provisioning' | 'running' | 'stopped' | 'error', | |
| }] | |
| : [] | |
| } |
| } finally { | ||
| setCreatingWorkspace(null); | ||
| } | ||
| }, [csrfToken, onWorkspaceCreated]); |
There was a problem hiding this comment.
The handleCreateWorkspace callback includes onWorkspaceCreated in its dependencies but doesn't use useCallback to memoize onWorkspaceCreated itself. This means the callback will be recreated whenever the parent component re-renders, potentially causing unnecessary re-renders of child components that depend on this callback.
| { headers: { Authorization: `Bearer ${apiToken}` } } | ||
| ); | ||
| const machine = stateRes.ok ? (await stateRes.json()) as { state: string } : { state: 'unknown' }; | ||
| throw new Error(`Machine ${machineId} did not start within ${timeoutSeconds}s (last state: ${machine.state})`); |
There was a problem hiding this comment.
The error message refers to timeoutSeconds but this variable is a parameter name that won't be meaningful to users. Consider using a more user-friendly message that explains what happened and what action to take, such as 'Machine failed to start within the configured timeout period. Please try again or contact support if the issue persists.'
| throw new Error(`Machine ${machineId} did not start within ${timeoutSeconds}s (last state: ${machine.state})`); | |
| throw new Error(`Machine ${machineId} failed to start within the configured timeout period (${timeoutSeconds}s). Please try again or contact support if the issue persists. Last known state: ${machine.state}`); |
| while (!authCode && !serverClosed && (Date.now() - startTime) < TIMEOUT_MS) { | ||
| await new Promise(resolve => setTimeout(resolve, 1000)); | ||
| const elapsed = Math.floor((Date.now() - startTime) / 1000); | ||
| if (elapsed > 0 && elapsed % 30 === 0) { | ||
| console.log(` Still waiting... (${elapsed}s)`); | ||
| } | ||
| } |
There was a problem hiding this comment.
This polling loop checks the condition every second for potentially 5 minutes (300 seconds by default), which is inefficient. Consider increasing the poll interval or implementing an event-based approach where the server notifies the client when the auth code is received.
|
|
||
| git add package.json package-lock.json | ||
|
|
||
| if git diff --staged --quiet; then | ||
| echo "No version changes to commit" | ||
| else | ||
| git commit -m "chore(release): v${NEW_VERSION}" \ | ||
| -m "NPM Tag: ${TAG}" | ||
|
|
||
| git push origin HEAD:main | ||
| echo "Version changes committed and pushed" |
There was a problem hiding this comment.
Pushing directly to the main branch bypasses any branch protection rules that may be in place. Consider creating a pull request or using a bot account with appropriate permissions, or ensure that the GitHub token used has the necessary bypass permissions configured.
| git add package.json package-lock.json | |
| if git diff --staged --quiet; then | |
| echo "No version changes to commit" | |
| else | |
| git commit -m "chore(release): v${NEW_VERSION}" \ | |
| -m "NPM Tag: ${TAG}" | |
| git push origin HEAD:main | |
| echo "Version changes committed and pushed" | |
| TARGET_BRANCH="${GITHUB_REF_NAME}" | |
| RELEASE_BRANCH_CREATED="false" | |
| git add package.json package-lock.json | |
| if git diff --staged --quiet; then | |
| echo "No version changes to commit" | |
| else | |
| # If running on main, create a dedicated release branch instead of pushing directly to main | |
| if [ "$TARGET_BRANCH" = "main" ] || [ "$TARGET_BRANCH" = "refs/heads/main" ]; then | |
| TARGET_BRANCH="release/v${NEW_VERSION}" | |
| git checkout -b "$TARGET_BRANCH" | |
| RELEASE_BRANCH_CREATED="true" | |
| fi | |
| git commit -m "chore(release): v${NEW_VERSION}" \ | |
| -m "NPM Tag: ${TAG}" | |
| git push origin "HEAD:${TARGET_BRANCH}" | |
| echo "Version changes committed and pushed to ${TARGET_BRANCH}" | |
| if [ "$RELEASE_BRANCH_CREATED" = "true" ]; then | |
| echo "A release branch '${TARGET_BRANCH}' was created. Please open a pull request to merge it into 'main'." | |
| fi |
Moved startCliPolling and handleCodeReceivedRef declarations before fetchCliSession to fix "used before declaration" TypeScript error. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
No description provided.