Skip to content

Expose app dev session files for agents - #8926

Draft
isaacroldan wants to merge 3 commits into
codex/dev-instance-supportfrom
make-dev-agentic
Draft

isaacroldan wants to merge 3 commits into
codex/dev-instance-supportfrom
make-dev-agentic

Conversation

@isaacroldan

@isaacroldan isaacroldan commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

WHY are these changes introduced?

Agents need to find an existing app dev session and read its current status and output from another terminal. This PR builds on the reusable CLI Kit API in #8952.

WHAT is this pull request doing?

Register one session per app configuration, capture process output in JSONL, and save the CLI's current preview status and errors. Non-interactive sessions remain registered until they stop, and dev work stops if session ownership or output fails.

The session files include the PID, session ID, URLs, log path, and details.preview. Preview status reflects the CLI's reported state. The agent workflow and integration tests are included here; file ownership, logging, and their unit tests are in #8952.

How to manually test your changes?

  1. Start shopify app dev for a configured app and development store. From another terminal, read .shopify/dev/*.json and the reported logFile.
  2. Start dev again with the same configuration and confirm it reports the existing session.
  3. Introduce an invalid extension import. Check details.preview.state and errors, then correct the import and check the new output and status.
  4. Stop dev and restart with --no-input. Confirm the session ID changes, the log starts fresh, and the session remains registered while dev runs. Stop it with SIGTERM and confirm the instance record is removed.

See docs/cli/app-dev-agent-workflow.md for the file-reading workflow.

Validation

The combined stack passes 99 focused tests, App lint and type checking, and the TypeScript build. Source and documentation match the pre-split implementation exactly. Its crash, pause/resume, status-write, log-load, and shutdown probes passed locally on macOS. No live-store test was performed.

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes
  • I've considered analytics changes to measure impact
  • Added a minor changeset for the app-dev behavior

@github-actions github-actions Bot added the Area: @shopify/cli @shopify/cli package issues label Oct 15, 2026
@isaacroldan

Copy link
Copy Markdown
Contributor Author

/snapit

@github-actions

Copy link
Copy Markdown
Contributor

🫰✨ Thanks @isaacroldan! Your snapshot has been published to npm.

Built from 710d4b4673dfebeba3ab9e6e08046905197986fc. Workflow run.

Test the snapshot by installing your package globally:

pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20261015153801

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@isaacroldan

Copy link
Copy Markdown
Contributor Author

/snapit

@github-actions

Copy link
Copy Markdown
Contributor

🫰✨ Thanks @isaacroldan! Your snapshot has been published to npm.

Built from 2b3b7ddf9a2ee813a1955a702160239f123499b9. Workflow run.

Test the snapshot by installing your package globally:

pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20261019115812

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@isaacroldan
isaacroldan force-pushed the make-dev-agentic branch 3 times, most recently from cfd91fb to b06cf68 Compare October 19, 2026 15:40
@isaacroldan isaacroldan changed the title Add a reusable dev-instance lock and JSONL log, and use it in app dev Expose app dev session files for agents Oct 19, 2026
@isaacroldan
isaacroldan changed the base branch from main to codex/dev-instance-support October 19, 2026 15:41
@isaacroldan
isaacroldan added this pull request to stack #8953 October 19, 2026 15:41
@isaacroldan

Copy link
Copy Markdown
Contributor Author

/snapit

@github-actions

Copy link
Copy Markdown
Contributor

🫰✨ Thanks @isaacroldan! Your snapshot has been published to npm.

Built from b06cf681d7a1a60c17630b5967ccc46ee43b213d. Workflow run.

Test the snapshot by installing your package globally:

pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20261019155212

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@github-actions github-actions Bot added Area: @shopify/app @shopify/app package issues and removed Area: @shopify/cli @shopify/cli package issues labels Oct 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

packages/cli-kit/dist/public/node/dev-instance.d.ts
import { Writable } from 'stream';
/**
 * A stable error code for JSON consumers when a long-running command refuses to start because another instance of
 * it is already running for the same project.
 */
export declare const DEV_INSTANCE_ALREADY_RUNNING_CODE = "dev_instance_already_running";
/**
 * Describes a running instance of a long-running command such as `app dev` or `theme dev`.
 * It's written to `.shopify/dev/<slug>.json` so agents and other tools can discover the instance.
 */
export interface DevInstanceInfo {
    schemaVersion: 1;
    /** Identifies this run, including when the OS reuses a PID. */
    sessionId: string;
    /** The command that owns the instance, for example `app dev`. */
    command: string;
    /** Distinguishes instances of the same command in the same directory, for example the app configuration file. */
    key?: string;
    pid: number;
    status: 'starting' | 'running';
    startedAt: string;
    updatedAt: string;
    /** The project directory the instance runs for. */
    directory: string;
    /** The JSON Lines file the instance appends its output to. */
    logFile: string;
    /** Command-specific, JSON-serializable details such as URLs and ports. */
    details: {
        [key: string]: unknown;
    };
}
/** One line of a dev instance's JSON Lines log. */
export interface DevInstanceLogEntry {
    timestamp: string;
    /** What produced the line, for example a process prefix like `web` or `extensions`. */
    source: string;
    /** The stream the line was written to, when it comes from a process. */
    stream?: 'stdout' | 'stderr';
    level?: 'debug' | 'info' | 'warning' | 'error';
    message: string;
}
export interface DevInstancePaths {
    /** The folder that holds every dev instance of the project: `.shopify/dev`. */
    folder: string;
    /** The instance description. */
    infoFile: string;
    /** The lock directory, owned by `proper-lockfile`. */
    lockFile: string;
    /** The JSON Lines log. */
    logFile: string;
}
export interface DevInstanceIdentity {
    /** The command that owns the instance, for example `app dev`. */
    command: string;
    /** The project directory. */
    directory: string;
    /** Distinguishes several instances of the same command in the same directory. */
    key?: string;
}
export interface AcquireDevInstanceOptions extends DevInstanceIdentity {
    /** Initial command-specific details. */
    details?: {
        [key: string]: unknown;
    };
}
export interface DevInstanceUpdate {
    status?: DevInstanceInfo['status'];
    /** Merged into the existing details. */
    details?: {
        [key: string]: unknown;
    };
}
export interface DevInstance {
    readonly info: DevInstanceInfo;
    readonly paths: DevInstancePaths;
    /** Aborts when this session loses its lock or output fails. Consumers must stop their owned work. */
    readonly abortSignal: AbortSignal;
    /** Updates and persists the instance description. */
    update: (changes: DevInstanceUpdate) => Promise<void>;
    /** Appends one entry to the JSON Lines log. */
    log: (entry: Omit<DevInstanceLogEntry, 'timestamp'>) => Promise<void>;
    /**
     * Wraps a stream so every line written to it is also appended to the log, without ANSI styling.
     * The returned stream forwards the original bytes to `target` unchanged.
     */
    tee: (target: Writable, options: {
        source: string;
        stream: 'stdout' | 'stderr';
    }) => Writable;
    /** Closes the log, removes the instance description and releases the lock. Safe to call more than once. */
    release: () => Promise<void>;
}
/**
 * Returns the files used by a dev instance. Every instance of a project shares `.shopify/dev`,
 * with one `<slug>.json`, `<slug>.lock` and `<slug>.jsonl` per command and key.
 *
 * @param identity - The command, directory and optional key of the instance.
 * @returns The paths of the instance files.
 */
export declare function devInstancePaths(identity: DevInstanceIdentity): DevInstancePaths;
/**
 * Marks this process as the running instance of a long-running command for a project.
 *
 * Mutual exclusion uses `proper-lockfile`: an atomic `mkdir` lock whose mtime this process refreshes as a heartbeat.
 * An exclusive startup directory serializes abandoned-lock recovery. An interrupted startup requires explicit
 * cleanup of that directory. A live owner blocks takeover, including when its heartbeat is stale.
 *
 * @param options - The instance identity, initial details.
 * @returns The acquired instance.
 * @throws AbortError with code {@link DEV_INSTANCE_ALREADY_RUNNING_CODE} when another live instance holds the lock.
 */
export declare function acquireDevInstance(options: AcquireDevInstanceOptions): Promise<DevInstance>;
/**
 * Returns the running instance of a command for a project, if there is one.
 *
 * @param identity - The command, directory and optional key of the instance.
 * @returns The instance description, or undefined when no live instance holds the lock.
 */
export declare function readDevInstance(identity: DevInstanceIdentity): Promise<DevInstanceInfo | undefined>;
/**
 * Lists every running dev instance of a project, across commands and keys.
 *
 * @param directory - The project directory.
 * @returns The descriptions of the live instances.
 */
export declare function listDevInstances(directory: string): Promise<DevInstanceInfo[]>;
/**
 * Checks whether a process exists. A process owned by another user counts as alive.
 *
 * @param pid - The process ID.
 * @returns True when the process exists.
 */
export declare function isProcessAlive(pid: number): boolean;

Existing type declarations

We found no diffs with existing type declarations

This branch has not been deployed

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

Labels

Area: @shopify/app @shopify/app package issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant