Files
PaperClipAI/tests/hiring-ai-connections
DottaandPaperclip f912ecaacf fix: carry AI connections through hiring and unblock task execution (#13438)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents hire other agents and assign tasks to them.
> - Managed AI connections must follow those hires across legacy and
native runners.
> - Missing accounts should pause task execution and let the user
connect from the task.
> - Subscription contention must wait without asking for new
credentials.
> - This pull request fixes these paths and the native tool and Daytona
staging failures found during live tests.
> - The result is a working hire, subtask, and connection setup flow on
local and remote runners.

## Linked Issues or Issue Description

**What happened?**

A managed Claude or Codex agent could hire a teammate without a usable
AI binding. Cross-provider hiring could fail before the user had a
chance to connect the new provider. First-time task setup did not show
the existing AI credential form inline. A busy subscription could
request a new connection. Native API replies could stop the parent after
a hire had already committed. Fresh Daytona sandboxes could fail to
extract read-only skill directories created on macOS.

**Expected behavior**

Compatible hires inherit the managed connection choice. A hire for
another provider uses the responsible user's default. If that account is
missing, the hire succeeds and the task asks for a connection.
Completing setup in the task resumes work automatically. Explicit child
auth settings and existing unmanaged login paths keep precedence.
Shared-account access checks remain in force.

**Steps to reproduce**

1. Connect a Claude or Codex parent with a managed AI account.
2. Ask it to hire one agent of each provider and create a self-assigned
subtask.
3. Assign work to both hires without connecting the second provider
first.
4. Connect the missing provider from its task card.
5. Check that all tasks finish and same-provider work uses the original
account.
6. Repeat with native runners and fresh Daytona sandboxes. The opt-in
browser suite in `tests/hiring-ai-connections/README.md` performs these
steps.

**Paperclip version or commit**

The live failures were reproduced from `f2c5e54dc`. The branch is
rebased onto `5282cabde`.

**Deployment mode**

Isolated local development instance. Legacy CLI and native runners.
Local execution and ephemeral Daytona sandboxes.

Related work: Refs #13247 for managed AI connections. Refs #13268 for
legacy credential-reference inheritance, which this branch preserves.
Refs #13432 for a concurrent managed-inheritance fix. This PR also
covers cross-provider task setup, subscription waits, native API
replies, and Daytona extraction. It permits missing responsible-user
defaults at hire time; restricted shared selections still fail.

## What Changed

- Apply managed connection defaults to both agent creation routes.
Preserve explicit auth choices and legacy credential-reference
inheritance.
- Allow hires before their responsible user connects the provider. Keep
approval gates, company boundaries, and shared-account access checks.
- Reuse the production AI credential form inside the pending task card.
Resume the task after setup.
- Retry subscription lease contention without consuming the
provider-failure allowance or creating a connection request.
- Require task execution-lock ownership when scheduling, promoting, and
dispatching subscription retries. Recheck ownership under the issue row
lock.
- Rename the HTTP operation identity at the native tool boundary so it
cannot override the runner's operation identity.
- Delay directory permission restoration during Daytona extraction.
Preserve the final read-only modes.
- Add database-backed regressions, real browser acceptance tests, and
Storybook states. Document setup and run-log behavior.

## Verification

- Six real browser scenarios passed: both parent providers on legacy
local and legacy Daytona; native Codex locally; native Claude on
Daytona. Each scenario hires both providers, completes a self-subtask
and assigned work, and connects the missing provider inline with
automatic continuation.
- Successful runs verify the account, responsible user, runner mode, and
Daytona lease. All 18 test sandboxes were deleted.
- Live authentication used API keys. Subscription inheritance, lease
contention, and retry have integration coverage. Fresh subscription
OAuth sign-in was not automated.
- Red/green tests reproduced missing bindings, missing inline forms,
subscription contention, native API reply failure, and GNU tar
permission failure.
- Seven Storybook browser checks passed. They cover both providers,
method selection, narrow layout, completion, cancellation, and invalid
credentials.
- Full local suite coverage completed before rebase. Initial timing and
fixture startup failures passed unchanged on isolated reruns. The first
full command did not exit cleanly; the remaining workspace and
serialized groups were completed separately.
- After rebase, 107 hiring/auth/retry tests and 59 native API,
task-card, and Daytona tests passed. The full workspace typecheck,
production build, and token gates passed again. Storybook build passed
before rebase.

- Review fixes: 169 hiring/retry/dispatch tests, 37 adjacent tests, and
four explicit cancellation-race cases passed. Eight cross-provider cases
cover stale auth keys on both creation routes and both runner types.
Server typecheck and build passed.

- Final CI on `ee4890837a8a4913e07453392b9a75969580dae1`: 32 checks
passed. Two optional Storybook jobs were skipped. The full server,
workspace, browser, native runner, build, typecheck, and release checks
passed.
- Three unchanged tests initially failed on a busy port, a chat row-lock
race, and preview-server readiness. Each affected job passed after one
CI rerun. Isolated local checks also passed: 41 credential tests, the
chat-concurrency case, and 25 preview-runtime tests.
- Greptile reviewed the final commit at 5/5. Both review threads are
resolved. GitHub reports no merge conflicts.

## Risks

- A missing personal account now defers authentication to the first
task. Explicit incompatible bindings and restricted shared accounts
still fail at hire time.
- An inherited personal default uses the responsible user's existing
authorization to install access for the new agent. It never copies
credentials or another user's identity.
- Subscription contention retries after a delay and rechecks task
eligibility. It does not consume the provider-failure budget.
- Native hiring uses the existing managed API-tools opt-in. Remote
native runners require a matching Linux binary and provider pack, as
documented in the acceptance README.
- No schema changes. Live tests make paid provider calls and remain
opt-in.

## Model Used

OpenAI GPT-6 (`gpt-6-astra`) in Codex, with reasoning, repository
inspection, code execution, browser automation, and API tools. The
context-window size is not exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-09-14 16:15:48 -05:00
..

AI connections through hiring

Paid, opt-in acceptance against a separate loopback local_trusted instance. Each scenario creates its own company and accounts. It never edits an existing company. Do not start the server with provider API keys: ambient credentials could mask missing managed-connection bindings.

Start from a fresh worktree with dependencies installed:

pnpm exec tsx cli/src/index.ts test-drive --data-dir /tmp/paperclip-hiring-qa --no-browser

Use the URL printed at startup. Test-drive may choose another available port when restarted. In a separate shell, export ANTHROPIC_API_KEY and OPENAI_API_KEY, then:

HIRING_AI_LIVE=1 HIRING_AI_TEST_URL=http://127.0.0.1:3100 \
  pnpm exec playwright test --config tests/hiring-ai-connections/playwright.config.ts --grep 'local:'

For both Claude and Codex, the browser creates a task instructing the real parent to hire one agent of each provider and create a self-assigned subtask. The server's activity log must attribute both hires to the parent agent. The test verifies the subtask and a task assigned to the same-provider hire finish under the original managed account. It then assigns work to the other provider, checks the pending AI connection card, connects with an API key in the card, and verifies the task resumes and finishes without sending another message or clicking retry. Completion comments must be authored by the assigned agent; account and responsible-user attribution are checked on the successful runs.

Provider calls use real credentials and incur usage. The fixture uses Claude Sonnet 4.6 and Codex GPT-5.4 through their CLI adapters. Provider subscription sign-in is not automated by this live suite. Integration tests cover subscription inheritance, credential serialization, and automatic retry after the parent's lease is released.

For representative coverage of the newer native runner, put PAPERCLIP_RUNNER_API_TOOLS_ENABLED=true in the disposable data directory's instances/default/.env, then restart test-drive. Test-drive clears inherited PAPERCLIP_* variables before loading that file. The existing opt-in managed API tools are how native agents hire. Then use:

HIRING_AI_LIVE=1 HIRING_AI_RUNNER=native HIRING_AI_TEST_URL=http://127.0.0.1:3100 \
  pnpm exec playwright test --config tests/hiring-ai-connections/playwright.config.ts --grep 'local: native: Codex'

This uses native Codex and native ACPX/Claude. It verifies native run persistence, managed API hiring, native create_task delegation, and paperclip_finish completion. Set HIRING_AI_ENVIRONMENT=daytona to exercise the same native path remotely. The default remains legacy CLI adapters; it also asserts that successful runs stayed on the legacy path. A single native parent scenario exercises both native worker providers without repeating the full matrix.

Daytona

Build the plugin and provide DAYTONA_API_KEY plus an immutable, qualified runner image digest. See tests/runner-e2e/README.md for the image publication workflow.

pnpm -C packages/plugins/sandbox-providers/daytona build
HIRING_AI_LIVE=1 HIRING_AI_ENVIRONMENT=daytona \
  HIRING_AI_TEST_URL=http://127.0.0.1:3100 \
  HIRING_AI_DAYTONA_IMAGE=ghcr.io/paperclipai/paperclip-daytona-runner@sha256:YOUR_DIGEST \
  pnpm exec playwright test --config tests/hiring-ai-connections/playwright.config.ts --grep 'daytona:'

The same tests install the plugin only in the disposable instance, create a Daytona environment, and assign it to the parent and both hires. Successful runs must report that environment. The finalizer cancels remaining active runs in the test company and deletes the environment and its leases. Task history and encrypted connections remain in the disposable data directory for inspection.

Native Daytona also requires the controller's remote runner binary and provider pack. On a Mac, use the Linux artifacts from the same qualified image, not the local runner binary. The pack must match this checkout's provider pins and profile digests; a newer image is not automatically compatible. Extract into an empty destination directory without starting the container:

mkdir -p /tmp/hiring-native-artifacts
hiring_image_container=$(docker create "$HIRING_AI_DAYTONA_IMAGE" /bin/true)
docker cp "$hiring_image_container:/opt/paperclip-runner/provider-pack" /tmp/hiring-native-artifacts/provider-pack
docker cp "$hiring_image_container:/usr/local/bin/paperclip-runnerd" /tmp/hiring-native-artifacts/paperclip-runnerd
docker rm "$hiring_image_container"

Add these settings to the disposable instance's instances/default/.env, alongside the native API-tools flag, and restart test-drive:

PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH=/tmp/hiring-native-artifacts/provider-pack
PAPERCLIP_RUNNER_REMOTE_BINARY_PATH=/tmp/hiring-native-artifacts/paperclip-runnerd

Run the Daytona command with HIRING_AI_RUNNER=native and --grep 'daytona: native: Claude' for one representative native remote scenario. If no matching image is available, use the runnerd-build and provider-pack-build Docker targets from this checkout, following the runner E2E workflow's lockfile resolution. Configure both resulting Linux artifacts together; native startup can stage them over a different image's preinstalled artifacts. Mixing a newer runner binary with an older provider pack can fail the sidecar handshake even when the individual provider version pins match.

Regression and Storybook checks

pnpm exec vitest run server/src/__tests__/agent-hire-ai-connections.test.ts \
  server/src/services/legacy-execution-recovery.test.ts \
  server/src/services/execution-recovery-attempt.test.ts \
  server/src/services/native-runtime/runner-api.integration.test.ts \
  ui/src/features/connections/ConnectionIntentInteractionBody.test.tsx
pnpm --filter @paperclipai/ui exec storybook dev -p 6010 -c storybook/.storybook --ci --no-open
HIRING_AI_STORYBOOK_URL=http://127.0.0.1:6010 \
  pnpm exec playwright test --config tests/hiring-ai-connections/playwright.config.ts --grep storybook

The Storybook checks use the production card and credential components with fixture API responses. They cover both providers, API/subscription choice, a narrow viewport, completion, cancellation, and invalid credentials. They do not contact providers.

The Daytona plugin's file-sync.test.ts also exercises real GNU tar extraction of interleaved read-only skill directories. It runs on Linux and on macOS with gnu-tar installed. This guards the macOS-to-Daytona staging failure where directory permissions were restored before all skill files arrived, while asserting that the finished directory and files retain their read-only modes.

Screenshots and redacted account/run attribution are written under test-results/. Traces, videos, and automatic failure screenshots are disabled because credential fields are involved. Each Playwright invocation clears its output directory; use --output to preserve separate local, Daytona, and Storybook evidence. Keep credentials out of issue descriptions, console output, command-line arguments, and attachments.