Skip to content

worktree setup unconditionally runs pnpm install, breaking zero-dependency repos #430

Open

Description

@bradtaylorsf

worktree.ts:243 runs pnpm install on session-worktree creation regardless of whether the target repo has a package.json. In a deliberately dependency-free repo (plain Node, no package.json), pnpm exits 1 and the whole session aborts at setup.

Dry-run cannot catch this: install only happens on real worktree creation, so the config validates clean and the failure only appears on the real run.

Workaround found: SKIP_INSTALL=true alpha-loop run.

Suggested fix: skip install when no package.json/lockfile exists at repo root (or honor a setup_command: "" / detected-stack "unknown" as no-install), and surface install failures as a named pre-condition error instead of a mid-setup abort.

Found on alpha-loop 2.4.0, macOS, agent: claude.

Activity

  1. added
    readyIssue is ready for automated agent processing
    in-progressAgent is actively working on this issue
    and removed
    readyIssue is ready for automated agent processing
    on Aug 24, 2026
  2. self-assigned this
    on Aug 24, 2026
  3. added
    in-reviewImplementation complete, PR under review
    and removed
    in-progressAgent is actively working on this issue
    on Aug 24, 2026
  4. bradtaylorsf commented on Aug 24, 2026

    @bradtaylorsf
    OwnerAuthor

    Automated implementation complete.

    PR: #435
    Tests: PASSING
    Review: Attached to PR body.


    Processed by alpha-loop in 575s

  5. added a commit that references this issue on Aug 24, 2026
    847dae3
  6. bradtaylorsf commented on Aug 24, 2026

    @bradtaylorsf
    OwnerAuthor

    AI Implementation Notes

    warning: --full-auto is deprecated; use --sandbox workspace-write instead.
    Reading prompt from stdin...
    2026-08-24T14:45:11.234944Z ERROR codex_core::session::session: failed to load skill /Users/bradtaylor/Documents/GitHub/alpha-loop/.worktrees/session-epic-432-epic-make-an-unattended-run-safe-no-checks-on-issue-prs-install-aborts-dep-free-repos-auth-failure-burns-the-queue/.agents/skills/playwright-testing/SKILL.md: missing YAML frontmatter delimited by ---
    OpenAI Codex v0.144.1

    workdir: /Users/bradtaylor/Documents/GitHub/alpha-loop/.worktrees/session-epic-432-epic-make-an-unattended-run-safe-no-checks-on-issue-prs-install-aborts-dep-free-repos-auth-failure-burns-the-queue
    model: gpt-5.6-sol
    provider: openai
    approval: never
    sandbox: workspace-write [workdir, /tmp, $TMPDIR, /Users/bradtaylor/Documents/GitHub/alpha-loop/.git] (network access enabled)
    reasoning effort: high
    reasoning summaries: none
    session id: 01a0343b-857e-7641-b7cd-98aee488c7fa

    user
    You just implemented GitHub issue #430: worktree setup unconditionally runs pnpm install, breaking zero-dependency repos

    Original Requirements

    worktree.ts:243 runs pnpm install on session-worktree creation regardless of whether the target repo has a package.json. In a deliberately dependency-free repo (plain Node, no package.json), pnpm exits 1 and the whole session aborts at setup.

    Dry-run cannot catch this: install only happens on real worktree creation, so the config validates clean and the failure only appears on the real run.

    Workaround found: SKIP_INSTALL=true alpha-loop run.

    Suggested fix: skip install when no package.json/lockfile exists at repo root (or honor a setup_command: "" / detected-stack "unknown" as no-install), and surface install failures as a named pre-condition error instead of a mid-setup abort.

    Found on alpha-loop 2.4.0, macOS, agent: claude.

    Code Changes (first 5000 chars)

    diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
    index e443cd4..667183a 100644
    --- a/.github/workflows/test.yml
    +++ b/.github/workflows/test.yml
    @@ -4,7 +4,7 @@ on:
    push:
    branches: [master]
    pull_request:

    • branches: [master]
    • branches: [master, 'session/', 'agent/']

    jobs:
    type-check:
    diff --git a/src/lib/worktree.ts b/src/lib/worktree.ts
    index 578edf5..7d41837 100644
    --- a/src/lib/worktree.ts
    +++ b/src/lib/worktree.ts
    @@ -4,7 +4,9 @@
    import { existsSync, mkdirSync, symlinkSync, readlinkSync, unlinkSync, readFileSync, writeFileSync, appendFileSync } from 'node:fs';
    import { join, resolve, basename, relative, isAbsolute } from 'node:path';
    import { exec, shellQuote } from './shell.js';
    +import type { ExecResult } from './shell.js';
    import { log } from './logger.js';
    +import { detectPackageManager } from './init-scan.js';
    import { isWaitingFeedbackStatus } from './session-state.js';
    import type { SessionStatus } from './session.js';

    @@ -51,6 +53,41 @@ export type CleanupWorktreeResult = {
    reason?: string;
    };

    +export type WorktreePreconditionPhase = 'dependency-install';
    +
    +export type WorktreeCommandFailure = ExecResult & {

    • command: string;
      +};

    +/** A setup prerequisite failed before the worktree could enter the build stage. */
    +export class WorktreePreconditionError extends Error {

    • readonly phase: WorktreePreconditionPhase = 'dependency-install';
    • readonly command: string;
    • readonly exitCode: number;
    • readonly stdout: string;
    • readonly stderr: string;
    • readonly attempts: readonly WorktreeCommandFailure[];
    • constructor(attempts: WorktreeCommandFailure[]) {
    • const failure = attempts.at(-1);
    • if (!failure) {
    •  throw new Error('WorktreePreconditionError requires at least one failed command');
      
    • }
    • const detail = failure.stderr || failure.stdout || 'no command output';
    • super(
    •  `Dependency installation pre-condition failed at "${failure.command}" `
      
    •  + `(exit ${failure.exitCode}): ${detail}. `
      
    •  + 'Fix the dependency metadata or install command, or set skip_install: true when installation is intentionally unnecessary.',
      
    • );
    • this.name = 'WorktreePreconditionError';
    • this.command = failure.command;
    • this.exitCode = failure.exitCode;
    • this.stdout = failure.stdout;
    • this.stderr = failure.stderr;
    • this.attempts = attempts;
    • }
      +}

    const ENV_FILES = ['.env', '.env.local', '.env.development', '.env.development.local'];

    function isInsidePath(parent: string, child: string): boolean {
    @@ -237,17 +274,25 @@ export async function setupWorktree(options: SetupWorktreeOptions): Promise<Work
    // Set COMPOSE_PROJECT_NAME so Docker doesn't use "issue-N" as project name
    ensureComposeProjectName(worktreePath, projectDir);

    • // Install dependencies unless skipped
    • if (!skipInstall) {
    • // Install dependencies only for projects with recognized package metadata.
    • const hasPackageMetadata = detectPackageManager(worktreePath) !== 'unknown';
    • if (!skipInstall && hasPackageMetadata) {
      log.info('Installing dependencies in worktree...');
    • const installResult = exec('pnpm install --frozen-lockfile', { cwd: worktreePath });
    • const frozenCommand = 'pnpm install --frozen-lockfile';
    • const installResult = exec(frozenCommand, { cwd: worktreePath });
      if (installResult.exitCode !== 0) {
      // Fall back to regular install
    •  const fallback = exec('pnpm install', { cwd: worktreePath });
      
    •  const fallbackCommand = 'pnpm install';
      
    •  const fallback = exec(fallbackCommand, { cwd: worktreePath });
       if (fallback.exitCode !== 0) {
      
    •    throw new Error(`Dependency installation failed (exit ${fallback.exitCode})`);
      
    •    throw new WorktreePreconditionError([
      
    •      { command: frozenCommand, ...installResult },
      
    •      { command: fallbackCommand, ...fallback },
      
    •    ]);
       }
      
      }
    • } else if (!skipInstall) {
    • log.info('Skipping dependency installation: no package.json or recognized lockfile found at worktree root');
      }

    // Run custom setup command (e.g., Python venv, Ruby bundler, Go modules)
    diff --git a/tests/lib/worktree.test.ts b/tests/lib/worktree.test.ts
    index 9c4b2eb..83e5a00 100644
    --- a/tests/lib/worktree.test.ts
    +++ b/tests/lib/worktree.test.ts
    @@ -1,4 +1,4 @@
    -import { setupWorktree, cleanupWorktree } from '../../src/lib/worktree';
    +import { WorktreePreconditionError, setupWorktree, cleanupWorktree } from '../../src/lib/worktree';

    // Mock dependencies
    jest.mock('../../src/lib/shell', () => ({
    @@ -41,9 +41,13 @@ const mockWriteFile = writeFileSync as jest.MockedFunction

    beforeEach(() => {
    jest.clearAllMocks();

    • // Default: commands succeed, paths don't exist
    • // Default: commands succeed and the synthetic worktree is a Node project.
      mockExec.mockReturnValue({ stdout: '', stderr: '', exitCode: 0 });
    • mockExists.mockReturnValue(false);
    • mockExists.mockImplementat

    Review Summary

    Issue #430 is fully addressed: dependency installation is skipped for repositories without package metadata and install failures are surfaced as typed pre-condition errors.

    Analyze the implementation and list any assumptions or decisions you had to make where the requirements were ambiguous or incomplete. Output ONLY a markdown document with this structure:

    Assumptions

    • (list each assumption made, e.g. "Assumed the date format should be ISO 8601 since it wasn't specified")
    • If no assumptions were needed, write "None — requirements were fully specified"

    Decisions

    • (list each design/implementation decision where multiple valid approaches existed, e.g. "Chose to validate on the server side rather than client side for security")
    • If no notable decisions, write "None — implementation was straightforward"

    Items to Validate

    • (list specific things the user should check, e.g. "Verify the error message wording matches your team's style guide")
    • If nothing needs validation, write "None"

    Keep it concise. Only include genuinely ambiguous items, not obvious implementation choices.
    codex

    Assumptions

    • Assumed detectPackageManager(...) !== 'unknown' accurately represents package metadata at the repository root.
    • Assumed dependency installation should be skipped for all unrecognized or dependency-free repositories, while custom setup commands may still run.
    • Assumed skip_install: true is the preferred user-facing override, despite the reported workaround using SKIP_INSTALL=true.

    Decisions

    • Chose metadata detection instead of adding special handling for setup_command: "" or the detected stack.
    • Preserved the existing pnpm frozen-install-then-fallback behavior rather than selecting the detected package manager.
    • Introduced an exported typed error containing both install attempts and the final failure details.
    • Expanded pull-request CI branch filters to include session/** and agent/**.

    Items to Validate

    • Verify detectPackageManager recognizes every intended root lockfile without classifying unrelated repositories as Node projects.
    • Confirm always invoking pnpm is correct when metadata indicates npm, Yarn, or another package manager.
    • Confirm the skip_install: true guidance and error wording match the supported configuration interface.
    • Confirm the additional CI branch filters are intended scope for issue worktree setup unconditionally runs pnpm install, breaking zero-dependency repos #430.
      tokens used
      12,175

    Assumptions

    • Assumed detectPackageManager(...) !== 'unknown' accurately represents package metadata at the repository root.
    • Assumed dependency installation should be skipped for all unrecognized or dependency-free repositories, while custom setup commands may still run.
    • Assumed skip_install: true is the preferred user-facing override, despite the reported workaround using SKIP_INSTALL=true.

    Decisions

    • Chose metadata detection instead of adding special handling for setup_command: "" or the detected stack.
    • Preserved the existing pnpm frozen-install-then-fallback behavior rather than selecting the detected package manager.
    • Introduced an exported typed error containing both install attempts and the final failure details.
    • Expanded pull-request CI branch filters to include session/** and agent/**.

    Items to Validate

    • Verify detectPackageManager recognizes every intended root lockfile without classifying unrelated repositories as Node projects.
    • Confirm always invoking pnpm is correct when metadata indicates npm, Yarn, or another package manager.
    • Confirm the skip_install: true guidance and error wording match the supported configuration interface.
    • Confirm the additional CI branch filters are intended scope for issue worktree setup unconditionally runs pnpm install, breaking zero-dependency repos #430.

    Posted by alpha-loop for user validation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

in-reviewImplementation complete, PR under review

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions