Skip to content

[codex] Share Docker agent auth across containers#2

Draft
brooksc wants to merge 27 commits into
mainfrom
docker-dotfiles
Draft

[codex] Share Docker agent auth across containers#2
brooksc wants to merge 27 commits into
mainfrom
docker-dotfiles

Conversation

@brooksc
Copy link
Copy Markdown
Owner

@brooksc brooksc commented May 2, 2026

Summary

Docker-mode agents currently start with a fresh writable home each time, so users have to re-sign into Claude, Codex, Gemini, OpenCode, or Copilot for every new Docker container. That repeated authentication overhead makes Docker isolation less appealing, especially when creating multiple worktrees or trying short-lived tasks.

This PR adds an opt-in setting to share agent auth across Linux containers. When enabled, the app creates a user-owned host directory under ~/.parallel-code/agent-auth/<agent> and bind-mounts it into that agent's config directory inside Docker, such as /tmp/.codex or /tmp/.claude. Signing in once inside a Docker container then persists for later Docker containers of the same agent type, including containers launched from different worktrees.

Implementation

  • Adds a persisted shareDockerAgentAuth setting and a checkbox in Settings.
  • Forwards the setting through the terminal spawn IPC path with main-process validation.
  • Mounts per-agent auth directories for known agent commands only.
  • Uses host bind directories instead of Docker named volumes so the container process, which runs as the host UID/GID, can write credentials on first login.
  • Covers enabled, disabled, and unknown-agent behavior in the Docker PTY tests.

Limitations

  • This does not copy or seed credentials from existing host config directories like ~/.codex or ~/.claude; it persists credentials created inside Docker after the setting is enabled.
  • Auth is shared per agent type, not per project or worktree.
  • Only the known built-in agent command names get auth mounts. Custom or differently named wrappers are intentionally ignored unless added to the mapping.
  • Existing running containers are unaffected; the mount is applied when a Docker agent is spawned.

Validation

  • npm test -- electron/ipc/pty.test.ts
  • npm run typecheck
  • npm run lint -- --quiet
  • npm run format:check
  • Commit and push hooks also ran npm run check.

ASRagab and others added 25 commits April 29, 2026 12:54
Drag a file or paste an image-from-Finder into the terminal and the
absolute path is typed (escaped, then wrapped via term.paste so xterm
emits bracketed-paste markers). CLI agents like Claude Code see the
path as a paste rather than literal typing, which triggers their
file-attachment recognition and turns "/Users/.../image.png" into
[Image #N] instead of a "no such file" error.

Implementation:
- new IPC ResolveClipboardPaste — main-process picks file URL → image
  → text in priority order, so Finder-copied image files pass an
  absolute path instead of the bare basename
- new IPC SaveDroppedImage — base64 round-trip (renderer's invoke()
  wrapper destroys typed arrays via JSON.parse(JSON.stringify())) so
  browser-origin <img> drops still produce a usable temp path
- webUtils.getPathForFile exposed via preload as
  window.electron.getPathForFile (Electron 32+ replacement for
  File.path)
- TerminalView capture-phase dragover/drop listeners so xterm's own
  bubble-phase handler doesn't insert the basename first
- escapePath helper backslash-escapes whitespace + shell metacharacters
  so paths round-trip cleanly through both POSIX shells and agent
  prompt parsers
- legacy SaveClipboardImage handler removed

Linux clipboard support reads x-special/gnome-copied-files (Files,
Nemo, Caja) ahead of text/uri-list (KDE, Xfce). Per-file resolution
runs in its own try/catch so one unreadable item in a mixed drop
doesn't cancel the resolvable siblings. Temp filenames append a 6-char
random suffix so concurrent same-name drops don't collide.

Spec captured under openspec capability terminal-image-paste (6
requirements, 32 scenarios) plus an archived change record at
openspec/changes/archive/2026-04-28-support-paste-images/.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Panels gated their focus border on `isActive && focusedPanel[id] === '...'`,
which stayed true after the sidebar grabbed focus. Centralize the predicate
in `isPanelFocused`/`isPanelFocusedPrefix` so sidebar/placeholder focus also
suppresses the border. Also enter the leftmost task on Right from sidebar
and drop the dead `data-shell-focused` attribute.
Add Copilot CLI to the supported-agents list, expand "More features"
with focus mode, steps panel, PR CI watcher, notes-to-agent, worktree
import, project Dockerfiles, coverage radar, configurable keybindings,
and folders-without-git support. Bump theme count from 6 to 10. Add
Ctrl+Shift+F to the shortcuts table.
The Terminal and bookmark buttons stopped click propagation, which kept
the parent's focusedPanel handler from running while their actions did
not touch focus — so any previously focused panel hung onto its border
until the user clicked elsewhere.
TUI renderers (Claude Code, etc.) pad each rendered line out to terminal
width and emit real \n at wrap points, so the clipboard ends up with
ragged trailing whitespace and mid-paragraph hard breaks. Cmd+C /
Ctrl+Shift+C now run the selection through a deterministic pipeline:
normalize CR/CRLF, strip per-line trailing whitespace, then reflow
paragraphs whose interior lines are uniformly long (≥40 chars, variance
≤8). Haiku-shaped and tabular content stays intact.

Hooked at both the keybinding handler and a capture-phase DOM `copy`
listener — the latter catches Electron's default Edit→Copy menu role,
which fires a synthetic copy event that bypasses
attachCustomKeyEventHandler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a branch's history contains commits that are patch-equivalent to
ones already on main (e.g. from a `git merge origin/main` followed by a
rebase that re-applies those merged-in commits with new SHAs), the
changed-files view used to surface them as branch work.

Add a cherry-pick refinement layer on top of the existing merge-base
picker: filter unique commits via `git log --cherry-pick --right-only`
and, when they're contiguous at the tip, refine the diff base to the
oldest unique commit's parent. Falls back to the picked base on
interleaving or any failure.
…k calls

Address review findings on the cherry-pick refinement layer:

- When `git log --cherry-pick` returns no unique commits the branch is
  fully merged upstream — return `head...head` instead of the picked
  base, so the user sees zero changes instead of the patch-equivalent
  noise (`base...head` would still surface every duplicate file).
- Embed parent SHA via `--pretty=%H %P` and drop the separate
  `git rev-parse <sha>^` call (1 fewer git invocation per refinement).
- Log the interleaved fallback path so it's diagnosable.
- Rename `mergeBaseCache` → `diffBaseCache` (now stores PickedMergeBase
  post-refinement, not just the raw merge-base SHA).

Tests: flip the fully-merged assertion to expect the empty range, add a
multi-commit contiguous case, and cover the from-branch refinement path.
Chevron-left from "All" now lands on a new "uncommitted only" view
before stepping into commit history; a U pill sits left of the All pill
as a direct affordance. The diff viewer mirrors the state via a new
GetUncommittedFileDiffs IPC built from git diff HEAD plus untracked
pseudo-diffs (extracted shared helper).
`checkMergeStatus` counted `HEAD..mainBranch` raw, so a rebased branch
whose history carried patch-equivalents of recent main commits would
trigger a false "Rebase onto main first" prompt. Switch to
`rev-list --cherry-pick --right-only --no-merges HEAD...mainBranch`,
matching the cherry-pick filter the changed-files diff base uses, so
patch-equivalent commits stop counting as ahead.

Scope kept to local mainBranch — that's what mergeTask and rebaseTask
operate on, so the count tracks what the dialog's buttons can fix.
Multi-agent review (correctness reviewer, empirically reproduced on
git 2.43.0) caught that --no-merges silently drops merge commits whose
own content is unique to main (evil merges / non-trivial merge
resolutions). Combined with --cherry-pick --right-only this turned a
real "Rebase first" warning into a zero, hiding the rebase need from
the dialog.

The flag was a copy-paste from refineDiffBaseWithCherryPick, where
it's load-bearing because that helper parses %H %P and assumes single
parents. Here we only ask for --count, so the flag is gratuitous —
worse than that, it's wrong.

Tests: assert --no-merges is *absent* from the rev-list args. Folded
the >0-ahead test into a richer case that exercises the merge-tree
conflict probe (previously asserted only the count). Added a
docstring back-reference in refineDiffBaseWithCherryPick so the dual
filter is discoverable from either direction.
The isActive gate skipped both the initial fetch and the 5 s poll, so
non-active tasks had an empty file list until something inside the panel
was clicked. CommitNavBar buttons stopPropagation, so navigating with U
/ All / chevrons changed selectedCommit but never activated the task —
the panel stayed empty until you clicked its body.

Always run the initial refresh on mount and on input changes; keep
polling gated on isActive so off-screen tasks don't run a git pipeline
every 5 s.
console.* writes to process.stdout/stderr asynchronously, so an EPIPE
from a closed parent pipe surfaces as an unhandled stream 'error'
event the per-call try/catch can't catch. Install module-level
handlers that swallow EPIPE and re-throw anything else, so a routine
teardown (e.g. concurrently SIGTERMing us after vite dies) no longer
becomes an Uncaught Exception.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Embed ChangedFilesList in the diff viewer dialog so users can switch
between files without closing the dialog. Track the open file via an
activeFilePath signal mirrored from the scrollToFile prop, and pass it
into the list to highlight the current row. Forward coverageReportPath
through TaskPanel → DiffViewerDialog → ChangedFilesList so coverage
overlays stay in sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix Codex arguments

* Add Codex argument regression tests
- Use path.basename() for agent command lookup so full paths work
- Incorporate relDir into hostDir to avoid cross-contamination if an agent
  has multiple config dirs in future
- Create host auth dirs with mode 0o700 to restrict access to credentials
- Degrade gracefully if mkdirSync fails rather than aborting Docker spawn
- Add shareDockerAgentAuth to autosave snapshot so toggling persists immediately

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@brooksc brooksc force-pushed the docker-dotfiles branch from e3e5c62 to 4d12dd3 Compare May 5, 2026 01:21
brooksc and others added 2 commits May 4, 2026 19:53
Claude stores its main auth config in ~/.claude.json (at HOME level) in
addition to ~/.claude/ — without mounting this file, credentials written
in the first container are lost when the container exits and the next
container prompts for login again.

Also add a note to the new-task Docker info message when auth sharing is
enabled so users know credentials will carry over.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
An empty file causes Claude to reject it as invalid JSON on startup.
Seed it with a valid empty object so the container starts cleanly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

3 participants