Skip to content

One client owns a Mini App at a time #254

Closed

Description

@Fectivnfy112357

Parent

#251

What to build

Two clients sharing a data directory never run the same Mini App at once. The first one records
ownership; the second reports that another client is running it and refuses to start it.

Scope note

Reporting the refusal in the listing and in the picker moved to #256 with the surface that shows it: the
WebUI's Mini App service is not enabled yet, so a presentation built here would be unreachable, and ADR 0020
requires the service cell, the presenter and the view to land as one change. The refusal itself — the part
that prevents a second process over one plugin's data — is delivered here, and it reports the holding
client and its pid so #256 has something to render.

Acceptance criteria

  • A second client does not spawn a Mini App whose ownership record is live.
  • A record left behind by a crashed owner is reclaimable, so a dead owner cannot block the app
    forever.
  • A Mini App already running outside this client's record — including one the desktop client is
    running — is detected through its derived port answering the way the guard answers, and is
    refused with the same message.
  • Two different Mini Apps may be owned by two different clients at the same time; ownership is per
    Mini App, not global.
  • The refusal happens before spawn, so a race cannot produce two processes over one plugin's data.

Activity

  1. added
    ready-for-agentFully specified, ready for an AFK agent
    miniappWebUI Mini App hosting programme (ADR 0020)
    on Oct 26, 2026
  2. Fectivnfy112357 commented on Oct 26, 2026

    @Fectivnfy112357
    Author

    All four remaining criteria are proven and implemented; the fifth moved to #256 with your agreement
    because the surface that would show it does not exist yet.

    Branch feat/miniapp-ownership-probe, commits 85c94c49 → eb617131 → e3cc29e9, stacked on #262.

    1. A second client does not spawn a Mini App whose ownership record is live

    prepareRuntime takes ownership before the port loop and before any spawn
    (node-runtime.ts, acquireMiniAppOwner). A held endpoint throws
    MiniAppNodeRuntimeError('OWNED_ELSEWHERE', 'Mini App <id> is already running in <client> (pid <pid>)').

    Proven through the scripted-child harness
    (packages/local-runtime-v2/test/unit/miniapp/node-runtime-credential.test.ts):
    refuses a Mini App another client owns, without spawning a process — the second preparation rejects
    with OWNED_ELSEWHERE and the second child receives no init message at all.

    2. A record left behind by a crashed owner is reclaimable

    The proof is an exclusively bound endpoint per Mini App: a socket file on POSIX, a named pipe on
    Windows, named from the plugin id. Binding is the ownership; the OS releases it when the owner dies, so
    "record present, nobody listening" is stale by definition rather than by timestamp.

    The crash path is the interesting one: a dead owner leaves the socket file behind, so a failed bind is
    distinguished from a leftover by connecting. reclaims an endpoint left behind by an owner that died
    creates a real socket, closes the server without unlinking, and the acquisition succeeds.

    ADR 0020's ownership section now records this mechanism, including why this code does not copy the
    OAuth lease server's rm(endpoint) before binding: that would steal a live owner's endpoint.

    3. A Mini App already running outside the record is detected through its derived port

    probeMiniAppPortOwnership answers free | guarded | foreign — the guard's answer, not a bare connect:
    free for a closed port, guarded when the port answers 401 to an anonymous health request, foreign
    when something holds the port and does not answer like the guard, so a stray listener is never mistaken
    for a Mini App. Three cases in loopback-guard.test.ts run against a closed port, the guarded fixture,
    and a plain node:http server in a child process.

    4. Two different Mini Apps may be owned by two different clients at once

    Ownership is per plugin: lets two different Mini Apps be owned at the same time, and the endpoint is
    named from the plugin data directory rather than a global path.

    Moved to #256: reporting the refusal in the listing and the picker

    The ticket body now says so. Reason: the WebUI's Mini App service is not enabled yet
    (wiring.miniAppService), and ADR 0020 requires the cell, the presenter and the view to land as one
    change. A listing presentation built now would be unreachable code. What #256 receives is the readable
    refusal — client and pid — which this branch already produces.

    Gates at e3cc29e9

    $ pnpm run test:capabilities
    Test Files  237 passed (237)
         Tests  5833 passed | 18 skipped (5851)
    $ pnpm typecheck            # passed
    $ pnpm check:source         # 5457 files, passed
    

    Not run: pnpm verify in full; browser and release-package steps are left to CI. No PR was opened for
    this branch — it rides behind #262 and the ownership work continues into #255's consumer.

    Untested boundary

    The desktop client writes no ownership record, so its presence is detected only by its port answering
    like the guard. That is the whole of what criterion 3 can promise, and it is stated in ADR 0020.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    miniappWebUI Mini App hosting programme (ADR 0020)ready-for-agentFully specified, ready for an AFK agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions