Skip to content

Daemonize login OAuth listener#82

Merged
austin-denoble merged 13 commits intomainfrom
adenoble/daemonize-login-listener
Apr 2, 2026
Merged

Daemonize login OAuth listener#82
austin-denoble merged 13 commits intomainfrom
adenoble/daemonize-login-listener

Conversation

@austin-denoble
Copy link
Copy Markdown
Collaborator

@austin-denoble austin-denoble commented Apr 2, 2026

Reworks the OAuth login flow to work correctly in agentic contexts (Claude Code, Cursor, other AI coding agents) where stdin/stdout are not interactive TTYs.

Problem

The previous pc login flow blocked on stdin waiting for [Enter], and the auth URL was either hidden or percent-encoded in ways that made it hard for agents to extract. There was no machine-readable way to surface the URL and get a non-blocking result.

Solution: daemon-backed two-call pattern

First call (pc login --json): spawns a detached background daemon that owns the OAuth callback server on 127.0.0.1:59049, emits {"status":"pending","url":"...","session_id":"..."} to stdout, and exits immediately. The daemon keeps listening for the browser redirect.

Second call (pc login --json): detects the pending session, polls the daemon's result file until auth completes, then emits {"status":"authenticated","email":"...","org_id":"...","org_name":"...","project_id":"...","project_name":"..."}.

If the second call is killed before the daemon finishes, a third call resumes seamlessly — the daemon keeps running regardless.

Non-agentic users (pc login without --json, TTY stdout) are completely unaffected.

Key details

  • Session state is stored in ~/.config/pinecone/sessions/ (mode 0600, 5-minute TTL) — PKCE verifier is passed to the daemon via environment variable and never touches disk.
  • Atomic result writes use write-to-temp-then-rename to prevent partial reads in the polling loop.
  • pc target re-auth uses a Wait: true mode that blocks until the token is acquired (daemon path, but synchronous), printing the auth URL to stderr so it's visible without polluting stdout.
  • Windows daemon uses CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS so it survives terminal close.
  • TTY auto-detection is applied in GetAndSetAccessToken so all callers (including pc target) correctly use the daemon path in non-TTY environments.
  • Port conflict detection in the interactive path surfaces a clear error with the existing auth URL if a daemon is already holding the callback port.

Note

Medium Risk
Changes the CLI OAuth login flow to spawn a detached background process and persist session state/results on disk, which can impact authentication reliability across platforms and failure modes. Also modifies re-auth behavior in pc target to block until tokens are acquired.

Overview
Daemonizes pc login --json OAuth flow by introducing a hidden pc auth _daemon subcommand that runs the local callback server in a detached process, exchanges the auth code, stores the token, and writes a session result file.

Adds resumable, file-backed login sessions (sessions/ state+result) so JSON login can return immediately with {"status":"pending"...} and be polled/resumed on subsequent invocations, with cleanup/expiry handling and cross-platform process detachment.

Refactors token acquisition and post-auth setup: splits interactive vs JSON paths, adds Options.Wait for callers that must block until credentials exist, and updates pc target re-auth to use Wait: true while keeping JSON output behavior separate via RunPostAuthSetup.

Written by Cursor Bugbot for commit 596f676. This will update automatically on new commits. Configure here.

…ss to allow returning immediately to support agentic workflows
Comment thread internal/pkg/utils/login/login.go
Comment thread internal/pkg/utils/login/session.go
…o to JSON output for login flow and clean up URL, clean up non-atomic file for polling
Comment thread internal/pkg/utils/login/login.go Outdated
Comment thread internal/pkg/utils/login/login.go
Comment thread internal/pkg/utils/login/login.go
Comment thread internal/pkg/utils/login/login.go Outdated
Comment thread internal/pkg/utils/login/daemon.go
Comment thread internal/pkg/utils/login/login.go Outdated
Comment thread internal/pkg/utils/login/process_windows.go
Comment thread internal/pkg/utils/login/session.go
…daemon survives terminal close. move PKCE from being stored on disk to passing through an environment variable
Comment thread internal/pkg/utils/login/login.go
…nd resume that and give the user an informative error message with the auth URL
Comment thread internal/pkg/utils/login/login.go
Comment thread internal/pkg/utils/login/login.go
Comment thread internal/pkg/utils/login/login.go
…eturned internally, target should be handling its own output
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread internal/pkg/utils/login/login.go
@austin-denoble austin-denoble merged commit 2055dc5 into main Apr 2, 2026
8 checks passed
@austin-denoble austin-denoble deleted the adenoble/daemonize-login-listener branch April 2, 2026 18:10
austin-denoble added a commit that referenced this pull request Apr 6, 2026
…target` fixes (#83)

## Problem
After previous changes to daemonize the login flow and improve overall
I/O, there were still some rough edges when working with the CLI through
an agent. Lack of consistent `--json` flag representation was still an
issue for a few flags, primarily around not supporting a shorthand
option. The `pc target` command also needed changes to better support
"headless", or agentic operation effectively. In-client documentation
around the specifics of some of these operations also need to be
improved to better guide users and agents when authenticating.

## Solution
This PR improves the CLI's behavior in agentic/non-TTY contexts (Claude
Code, Cursor, other AI coding agents). It builds on the daemon-backed
login flow from #82

### `-j` shorthand added to login commands

`pc login` and `pc auth login` were missing the `-j` shorthand for
`--json`, inconsistent with every other command. Fixed.

### Lazy auth completion

Added `login.EnsureAuthenticated`, called from a `PersistentPreRun` hook
on the root command. After a successful browser login, the next command
run — any command — automatically detects the completed session, reloads
credentials, and initializes the target org/project context. A second
`pc login --json` call is no longer required before running other
commands.

Commands that don't require authentication are explicitly exempted: `pc
login`, `pc logout`, `pc auth
login/logout/configure/clear/status/_daemon`, `pc auth
local-keys`/`list`, `pc target` (which handles its own auth after
local-state early returns), `pc version`, and all `pc config`
subcommands.

### `pc target` fixes

- **TTY auto-detection** — `pc target` now detects non-TTY stdout and
enables JSON mode automatically, consistent with `pc login`.
- **No-flags JSON mode** — `pc target --json` with no targeting flags
now returns the current target context as JSON (equivalent to `--show
--json`) instead of an error. This early return is correctly placed
before auth and API calls, so it works for API-key users and requires no
credentials.
- **`--show` and `--clear` unblocked** — These are local-state-only
operations; they now return before the auth gate and before any API
calls.
- **Re-auth URL stays on stderr** — When `pc target` triggers an
org-switch re-auth, the auth URL is printed to stderr only. Stdout
remains a single JSON document, keeping output compatible with `jq .`
and other single-document parsers.
- **Missing `return` after `exit.Error`** — Added `return` guards after
all `exit.Error` calls in target and root pre-run, preventing
fall-through when the exit handler is mocked in tests.

### `applyAuthContext` / `RunPostAuthSetup` refactor

Extracted the org/project state-setting logic from `RunPostAuthSetup`
into a new `applyAuthContext` helper that returns the user's email. This
eliminates a redundant `oauth.Token` + `ParseClaimsUnverified` call that
`RunPostAuthSetup` was making immediately after `applyAuthContext` had
already fetched the same data. `applyAuthContext` now also always writes
`state.TargetProj` (clearing it when the org has no projects),
preventing stale project data from a previous session from appearing in
the authenticated JSON response.

### Root pre-run JSON error output

`EnsureAuthenticated` errors in the root `PersistentPreRun` now check
both TTY state and the command's own `--json`/`-j` flag, so auth errors
are machine-readable when a caller explicitly requests JSON even in a
TTY context.

### Deduplication

Extracted `printTargetContextJSON()` in `target.go`, replacing three
identical blocks that each read `state.GetTargetContext()`, masked the
API key, and printed JSON.

### Documentation

Updated `pc login`, `pc auth login`, and `pc target` help text to
document the interactive vs. agentic flows, the two-call
pending/authenticated pattern, session resumability, and the lazy
context initialization behavior. Also fixed a typo in `pc target`'s
example (`-project` → `--project`).

## Type of Change
- [X] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] This change requires a documentation update
- [ ] Infrastructure change (CI configs, etc)
- [ ] Non-code change (docs, etc)
- [ ] None of the above: (explain here)

## Test Plan
### Interactive login (TTY)
- [ ] `pc login` opens a browser and completes auth, sets target
org/project, prints success message
- [ ] `pc auth login` same as above
### Agentic login (non-TTY / `--json`)
- [ ] `pc login --json` returns
`{"status":"pending","url":"...","session_id":"..."}` and exits
immediately
- [ ] `-j` shorthand works identically on both `pc login` and `pc auth
login`
- [ ] Second `pc login --json` after completing browser auth returns
`{"status":"authenticated",...}` with org/project populated
- [ ] Running any other command (e.g. `pc index list --json`) after
browser auth — without a second `pc login` call — succeeds and sets
target context automatically
- [ ] Piping output (`pc login | cat`) triggers non-TTY path
automatically without needing `--json`
### Lazy auth / `EnsureAuthenticated`
- [ ] After `pc login --json` + browser auth, `pc index list --json`
works without a second login call
- [ ] `pc auth status` works with no credentials (not blocked by auth
gate)
- [ ] `pc auth local-keys list` works with no credentials (not blocked
by auth gate)
- [ ] `pc version` works with no credentials
- [ ] All `pc config` subcommands work with no credentials
                                                                     
### `pc target`
  - [ ] `pc target --show` works with no credentials                 
- [ ] `pc target --clear` works with no credentials
- [ ] `pc target --json` (no flags) returns current target context as
JSON, works with no credentials
- [ ] `pc target --json --org "name"` sets org and returns updated
context as JSON
- [ ] `pc target --json --org "name" --project "name"` sets both and
returns updated context
- [ ] `pc target` in a TTY launches interactive selector as before
- [ ] `pc target` in a non-TTY (or with `--json`) without targeting
flags returns current context rather than erroring
### Auth errors
- [ ] Running a command requiring auth with no credentials returns
`{"error":"..."}` to stdout when stdout is non-TTY
- [ ] Running a command requiring auth with no credentials and `--json`
flag returns `{"error":"..."}` to stdout even in a TTY
- [ ] "authentication in progress" error includes the auth URL when
daemon is still running

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adds a global authentication pre-check for most commands and changes
login/target behavior in non-TTY JSON mode, which could affect
automation and command execution paths if the skip list or auth
detection is incorrect.
> 
> **Overview**
> **Adds a root-level auth gate for most commands.** `pc` now runs
`login.EnsureAuthenticated()` in `PersistentPreRun`, with a curated skip
list for commands that manage credentials/local state, and auto-detects
JSON mode (non-TTY stdout or `--json/-j`) to format auth errors
appropriately.
> 
> **Improves non-interactive/agentic UX.** `login` and `auth login` gain
`-j` shorthand and updated help/examples; `target` now auto-enables JSON
mode in non-TTY contexts, supports `pc target --json` as an auth-free
“show current context” path, and centralizes JSON context printing.
> 
> **Refactors and extends post-auth setup.** Login utilities split
context initialization into `applyAuthContext()`, ensure target project
state is cleared when no projects exist, keep stdout clean during
wait-mode by printing auth URLs to stderr, and introduce
`EnsureAuthenticated()` to lazily complete pending daemon sessions and
auto-initialize org/project context after successful browser auth.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ddb7ecd. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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.

1 participant