dsh-codex-subs-plugin User Guide

August 14, 2026 · View on GitHub

中文 | English

dsh-codex-subs-plugin User Guide

This guide starts with an unconfigured DeepSeek Harness (DSH) environment and completes the following end-to-end path:

Build the plugin
  → install it into the web and headless profiles separately
  → sign in with ChatGPT OAuth
  → select codex-subscription / gpt-5.5
  → make a real CLI request
  → start the Web UI
  → select a workspace and model
  → make a real Web request

If you only need the shortest path, go directly to Quick Start. Before using this in production, also read Security Boundaries and Troubleshooting.

1. Scope

This plugin registers the Codex subscription capability of a ChatGPT account as a primary DSH LLM route:

provider: codex-subscription

It is not either of the following:

  • It is not an OpenAI Platform API-key adapter. OpenAI defines “Sign in with ChatGPT for subscription access” and “API key for usage-based access” as two separate authentication paths; see OpenAI authentication.
  • It is not DSH's built-in @deepseek-ai/dsh-subagent-codex. That package uses the official codex app-server as a Codex subagent; this plugin connects Codex to DSH's primary-model route.

This project depends on non-public, non-stable contracts such as the auth.openai.com device-code endpoints, ChatGPT-Account-Id, and chatgpt.com/backend-api/codex. Server-side changes may break the plugin, and a subscription does not imply unlimited usage.

2. Verified Baseline

A live verification was completed on 2026-08-14 with the following environment:

  • macOS
  • Node.js 24.13.1
  • DSH 0.1.0-rc.6
  • Plugin 0.1.0
  • Valid browser-OAuth credentials
  • gpt-5.5 with reasoningEffort: medium

Verification results:

Headless: codex-subscription-ok
Web:      codex-web-ok

No token, refresh token, account ID, or proxy address was printed or recorded in this document during verification. Model and account entitlements are determined dynamically by the server; these results prove only that this account and network path worked at the time of the test.

3. Prerequisites

3.1 Software

The plugin requires:

Node.js ^22.19.0 or >=24.0.0
pnpm
dsh CLI

Check the versions:

node --version
pnpm --version
dsh --version

On a new machine, you can install the versions verified by this guide:

npm install --global pnpm@11
npm install --global @deepseek-ai/dsh@0.1.0-rc.6

DSH is still in developer preview. After upgrading to another version, repeat the profile, doctor, headless, and Web acceptance checks in this guide.

DSH can also be launched directly through npm:

npx @deepseek-ai/dsh web

The remainder of this guide assumes dsh is already on PATH. If you do not have a global command, replace each dsh with a pinned npx --yes @deepseek-ai/dsh@<version> invocation.

3.2 Use One Fixed DSH_HOME

DSH profiles, settings, and this plugin's credentials all depend on DSH_HOME. Installation, login, diagnostics, headless operation, and Web operation must use the same value.

When unset, it defaults to:

~/.dsh

To set it explicitly:

export DSH_HOME="$HOME/.dsh"

Both DSH_HOME and DSH_CODEX_SUBS_AUTH_FILE should use absolute paths. The CLI converts a relative --auth-file path to an absolute path, but the adapter rejects a relative auth path. Mixing the two behaviors can produce a state where the CLI reports a successful login while the runtime still cannot find the credentials.

Key files:

FilePurpose
$DSH_HOME/settings.yamlRuntime settings and default model shared across profiles
$DSH_HOME/codex-subs/auth.jsonThis plugin's default OAuth credentials
$DSH_HOME/profiles/web/package.jsonWeb-profile dependencies and bundle list
$DSH_HOME/profiles/web/cordis.patch.ymlWeb-profile-specific plugin overrides
$DSH_HOME/profiles/headless/package.jsonHeadless-profile dependencies and bundle list
$DSH_HOME/profiles/headless/cordis.patch.ymlHeadless-profile-specific plugin overrides

web and headless are separate profiles. The plugin must be installed into each profile separately. The OAuth file and settings.yaml are shared by default, so you normally sign in once and configure the default model once.

4. Quick Start

Run the following commands from the plugin repository root:

pnpm install
pnpm run check

dsh plugin --profile web add .
dsh plugin --profile headless add .

Sign in:

dsh plugin --profile web exec dsh-codex-subs login

Login prints the complete authorization URL. If the credentials contain the corresponding metadata, a successful login also prints the email address and account ID. Run it only in your own terminal; do not run it in CI, a shared terminal, a screen recording, tee, or a shell trace.

If no graphical browser is available:

Device-code login is currently a beta feature and must be enabled first. For a personal account, enable device-code login under ChatGPT Settings → Security. For a managed workspace, an administrator must enable it in workspace permissions. If it is not enabled, use the browser login above. See OpenAI authentication.

dsh plugin --profile web exec dsh-codex-subs login --headless

Headless login prints a one-time user code. Do not share or record it.

Merge the following content into $DSH_HOME/settings.yaml; do not overwrite other namespaces already in the file:

agent-default-model:
  provider: codex-subscription
  model: gpt-5.5
  reasoningEffort: medium

Run diagnostics and one headless request:

dsh plugin --profile web exec dsh-codex-subs doctor
dsh plugin --profile headless exec dsh-codex-subs doctor
dsh --profile headless "Reply only: codex-subscription-ok"

Start the Web UI:

cd /absolute/path/to/your/project
dsh web

Open the URL printed in the terminal. The default is:

http://127.0.0.1:3080

Then:

  1. Select or add the project workspace.
  2. Create a new session.
  3. Click the current model in the composer.
  4. Open Model.
  5. Select OpenAI Codex Subscription → GPT-5.5.
  6. Confirm that Effort is Medium.
  7. Send Reply only: codex-web-ok.

When the page returns codex-web-ok, the complete Web path is verified.

5. Build and Install

5.1 Install from the Current Checkout

cd /absolute/path/to/dsh-codex-subs-plugin
pnpm install
pnpm run check
pnpm pack --dry-run

Install into the Web profile:

dsh plugin --profile web add .

If you also want to run the headless CLI smoke test, install it into the headless profile:

dsh plugin --profile headless add .

add . must be run from the plugin repository root. You can also use an absolute path:

dsh plugin --profile web add /absolute/path/to/dsh-codex-subs-plugin
dsh plugin --profile headless add /absolute/path/to/dsh-codex-subs-plugin

5.2 Verify CLI Installation

Check each profile separately:

dsh plugin --profile web exec dsh-codex-subs --help
dsh plugin --profile headless exec dsh-codex-subs --help

You should see:

Usage: dsh-codex-subs <command> [options]

If Web is already running, restart dsh web after installing or upgrading a profile bundle.

5.3 What --dump-config Proves

dsh --profile web --dump-config
dsh --profile headless --dump-config

The following lines in the output prove only that the plugin bundle has entered the Cordis composition tree:

- id: llm-codex-subscription
  name: dsh-codex-subs-plugin

--dump-config does not start the plugin, read $DSH_HOME/settings.yaml, or verify OAuth, proxy routing, model entitlement, or the actual runtime route. It is normal for the composed configuration to continue showing the DeepSeek default.

6. Proxy Configuration

Proxy variables must be set before running login, a headless request, or dsh web:

export HTTPS_PROXY=http://proxy.example:8080
export HTTP_PROXY=http://proxy.example:8080
export NO_PROXY=localhost,127.0.0.1

Supported variables:

  • HTTP_PROXY / http_proxy
  • HTTPS_PROXY / https_proxy
  • NO_PROXY / no_proxy

When both upper- and lower-case forms of the same variable are set, the lower-case form takes precedence. When HTTPS_PROXY is unset, the transport uses HTTP_PROXY as the fallback proxy for HTTPS.

Notes:

  • The current transport does not read ALL_PROXY.
  • The plugin uses a private Undici EnvHttpProxyAgent and does not depend on Node 24's NODE_USE_ENV_PROXY=1.
  • The browser authorization page uses the browser or operating-system network path. CLI code exchange, device flow, refresh, and Responses requests use the plugin transport. The two paths may have different public egress addresses.
  • It is generally wise to keep loopback in NO_PROXY so other environment clients do not proxy local traffic. However, the browser → 127.0.0.1 callback does not use this plugin's transport, and changing only the plugin's proxy variables cannot repair a browser callback. Do not accidentally add auth.openai.com or chatgpt.com to NO_PROXY and bypass a required CLI proxy.
  • Do not run set -x, printenv, env, or curl -v, and do not paste a proxy URL containing a username or password into logs.

Check presence without printing values:

dsh plugin --profile web exec dsh-codex-subs doctor

You should see output similar to:

Proxy variables detected: yes
Plugin env proxy transport: enabled (independent of Node env proxy)

7. OAuth Login and Credentials

7.1 Browser PKCE Login

dsh plugin --profile web exec dsh-codex-subs login

This command prints the complete authorization URL. If the credentials contain the corresponding metadata, successful output also prints the email address and complete account ID. Do not run it in CI, a shared terminal, a screen recording, tee, set -x, or any other shell trace.

The flow:

  1. Starts a local callback on 127.0.0.1:1455.
  2. Prints and attempts to open the OpenAI authorization URL.
  3. Validates OAuth state.
  4. Exchanges the PKCE authorization code for tokens.
  5. Shows success in the browser only after the atomic credential write has completed.

The browser flow has an overall timeout of five minutes. If the port is occupied, the browser cannot call back, or the machine has no desktop environment, use the device flow.

7.2 Headless Device Flow

Device-code login is currently a beta feature. A personal account must first enable device-code login under ChatGPT Settings → Security. A managed workspace requires an administrator to enable it in workspace permissions. If it is not enabled, use the browser PKCE flow instead. See OpenAI authentication for the official prerequisite.

dsh plugin --profile web exec dsh-codex-subs login --headless

The CLI displays an authorization URL and a one-time user code. The entire flow has a 15-minute timeout. The user code is sensitive while valid; do not record it or paste it into a shared chat.

7.3 Credential Path

Default path precedence:

$DSH_CODEX_SUBS_AUTH_FILE
  > $DSH_HOME/codex-subs/auth.json
  > ~/.dsh/codex-subs/auth.json

The file contains a bearer access token and a refresh token. It is not stored in Keychain and has no application-layer encryption. When creating storage, the plugin requests mode 0700 for the directory and 0600 for the file, and uses a temporary file, fsync, and an atomic rename.

The file may also contain an email address and account ID. mkdir(..., mode: 0700) does not automatically tighten an existing parent directory with broader permissions. Before first use, confirm that only you can access $DSH_HOME and the parent directory of any custom auth file.

Do not inspect or copy the auth JSON contents, and do not add the file to Git, a CI artifact, a log, an issue, or a chat.

7.4 status and doctor

Inspect local status:

dsh plugin --profile web exec dsh-codex-subs status

status does not make a network request or refresh a token. It prints the complete auth-file path and, when the credentials contain the corresponding metadata, the email address and complete account ID. Use it only in your own local terminal; do not share its output directly.

It exits with 0 when the credential exists and is unexpired according to the local clock. It exits with 1 when the credential is missing or locally expired. A nonzero exit does not mean that refresh has already been attempted.

Redacted diagnostics:

dsh plugin --profile web exec dsh-codex-subs doctor

doctor does not make a network request or refresh credentials. It reports only:

  • whether auth is present, active, or expired;
  • whether an account ID is present, without printing its value;
  • whether the plugin is detected in the current profile;
  • the provider/model declared in settings;
  • whether proxy variables are present, without printing their values;
  • the fixed endpoint.

active means only that the local file format is valid and expiresAt has not been reached. It does not prove that the token is still accepted remotely or that the account has entitlement to a model. Installation detection only checks whether package.json in the current working directory contains both the dependency and bundle; it does not prove that the adapter has started.

Account ID is optional metadata. The plugin sends the corresponding request header only when the value exists in the credentials. A missing account ID in doctor is not enough by itself to classify login or routing as failed; use a minimal real request as the deciding check.

doctor reads only the default $DSH_HOME/settings.yaml; it does not compose a custom settings file or the complete Cordis patch. Its displayed Provider is also fixed to the default value codex-subscription and does not resolve a custom route ID from the profile. When using advanced overrides, rely on the actual profile patch, the Web model selector, and a minimal real request.

When a report is generated successfully, diagnostic states such as missing, expired, or invalid currently do not change the exit code; all return 0. Argument errors, profile/bin execution failures, or unexpected exceptions can still return nonzero. Automation should check both the command exit code and the Auth status field in the output. Default output does not contain token values, identity values, or proxy values, but custom settings provider/model identifiers are printed as-is. Manually review the complete output before sharing it.

7.5 Custom Auth File

Use only an absolute path, and point the adapter and CLI to the same file.

In $DSH_HOME/profiles/<profile>/cordis.patch.yml for the target profile:

- id: llm-codex-subscription
  config:
    authFile: /absolute/private/path/codex-auth.json

Use the corresponding path for login and diagnostics:

dsh plugin --profile web exec dsh-codex-subs login \
  --auth-file /absolute/private/path/codex-auth.json

dsh plugin --profile web exec dsh-codex-subs doctor \
  --auth-file /absolute/private/path/codex-auth.json

dsh plugin --profile web exec dsh-codex-subs status \
  --auth-file /absolute/private/path/codex-auth.json

dsh plugin --profile web exec dsh-codex-subs logout \
  --auth-file /absolute/private/path/codex-auth.json

If both Web and headless use this path, apply the same plugin configuration to both profiles.

See the previous section for doctor limitations with custom providers and settings paths. If you change the route ID, you must also update agent-default-model.provider.

8. Select the Default Model

The plugin only registers a provider; it does not silently replace DSH's DeepSeek default model. You must select a model explicitly.

8.1 Edit settings.yaml Directly

Merge the following namespace into $DSH_HOME/settings.yaml:

agent-default-model:
  provider: codex-subscription
  model: gpt-5.5
  reasoningEffort: medium

If the file already contains ui-onboarding, another provider, or UI settings, preserve them. Do not use shell redirection that overwrites the entire file.

Supported default reasoning efforts:

off | low | medium | high | xhigh

8.2 Save Through the Web Session Selector

You can avoid editing YAML manually:

  1. Start Web and select a workspace.
  2. In a normal session composer, click the current model or Select model.
  3. Open Model.
  4. Select a model in the OpenAI Codex Subscription group.
  5. Open Effort and select a reasoning effort.

The Web selection applies to the next request in the current session and attempts to save the choice as the global default for future new sessions.

Do not configure this plugin by entering an API key under Settings → Models. That page is for settings-backed/API-key providers. This plugin must complete OAuth through dsh-codex-subs login, after which it appears in the session model selector.

8.3 Current Catalog

Current advisory catalog:

gpt-5.3-codex-spark
gpt-5.4
gpt-5.4-mini
gpt-5.5

This is only a UI-selection snapshot, not an entitlement allowlist. An unlisted model ID is still sent through unchanged at the lower layer, and the server may accept or reject it. Public OpenAI API model capabilities also do not prove that the private ChatGPT Codex backend exposes the same model or context window to the current account.

8.4 Session Model Semantics

  • A new session reads the latest agent-default-model.
  • Switching models in Web takes effect on the current session's next request.
  • An in-progress step does not switch models midway through generation.
  • An older session that has already sent requests retains the model recorded in its log. Changing the global default does not automatically rewrite older sessions.
  • If saving settings fails, the current session switch may still succeed, but the default will be lost after restart. Check doctor, then create a new session to confirm inheritance.

9. Headless Verification

Prerequisites: the plugin is installed into the headless profile, and settings select codex-subscription.

Static check:

dsh plugin --profile headless exec dsh-codex-subs doctor

Real online request:

dsh --profile headless "Reply only: codex-subscription-ok"

Expected output:

codex-subscription-ok

Together, this step verifies:

settings runtime selection
  → provider route
  → OAuth access/refresh
  → environment proxy
  → fixed Codex Responses endpoint
  → SSE translation
  → DSH output

It consumes subscription usage and writes a local DSH session log. The request is sent to OpenAI and processed under the ChatGPT workspace's retention, residency, and data-handling policies. Neither terminal redirection nor store:false should be interpreted as “zero server retention.”

If you only need a shareable success/failure status without model output or raw errors:

set +x
if dsh --profile headless "Reply only: codex-subscription-ok" >/dev/null 2>&1; then
  printf '%s\n' '{"onlineSmoke":"ok"}'
else
  smoke_rc=$?
  printf '{"onlineSmoke":"failed","exitCode":%d}\n' "$smoke_rc"
  exit "$smoke_rc"
fi

This still sends a request, consumes subscription usage, and writes a session log. During troubleshooting, inspect raw stderr only in your own terminal, then redact it manually before sharing.

10. Complete Web Flow

10.1 Start

Run from the project directory you want to use as a workspace:

cd /absolute/path/to/your/project
dsh web

Equivalent command:

dsh --profile web

Default bind address:

127.0.0.1:3080

Treat the terminal output as authoritative:

dsh web: http://127.0.0.1:3080

Change the port:

dsh web --port 3081

Select an available port automatically:

dsh web --port 0

The current DSH version explicitly rejects --host 0.0.0.0. Do not expose the Web UI to the public internet. --trusted-host configures browser Host/authority trust; it is not a remote-listening switch.

10.2 Add a Workspace

The composer is unavailable on first entry until you select a workspace:

  1. Click Choose workspace in the center of the page, or Add workspace in the sidebar.
  2. In the Select Workspace Directory dialog, click Edit path.
  3. Enter the project's absolute path.
  4. Press Enter to navigate to the directory.
  5. Click Open.

If the sidebar is collapsed, click Open sidebar first, then use Add workspace.

10.3 Select Codex

  1. Create a normal new session.
  2. Click the current-model button below the composer.
  3. Click Model in the menu.
  4. Find the OpenAI Codex Subscription provider group.
  5. Select GPT-5.5.
  6. Open the menu again and select Medium under Effort.

You can also enter /model in the composer to open the same model catalog.

When loaded correctly, the model catalog should contain:

OpenAI Codex Subscription
  GPT-5.3 Codex Spark
  GPT-5.4
  GPT-5.4 Mini
  GPT-5.5

10.4 Web Online Smoke Test

In a text-only session, send:

Reply only: codex-web-ok

Expected response:

codex-web-ok

Then click New session and confirm that the model button still shows:

GPT-5.5 · Medium

This also verifies that the Web model selection was persisted as the default for new sessions.

The current adapter declares text input only. Do not attach images during the smoke test; a request containing an image is rejected before it is sent.

11. Advanced Plugin Configuration

11.1 Configuration Merge Order

Every time DSH starts a profile, it composes Cordis configuration in the following order. Later layers take precedence:

  1. Bundle patches declared in dsh.profile.bundles inside $DSH_HOME/profiles/<profile>/package.json;
  2. $DSH_HOME/profiles/<profile>/cordis.patch.yml;
  3. $DSH_HOME/cordis.patch.yml, shared across profiles;
  4. --patch <path> overlays supplied on the command line, in order.

For the same id, a later layer's config replaces the entire earlier config; it is not recursively deep-merged. When overriding an existing complex plugin, carry forward every field that must be retained.

$DSH_HOME/settings.yaml is not part of the Cordis patch stack above. It is read by the running settings provider, which overlays user settings such as agent-default-model on the composed defaults. This is why --dump-config cannot show the runtime default model.

11.2 Plugin Fields

Each profile has a separate override file:

$DSH_HOME/profiles/web/cordis.patch.yml
$DSH_HOME/profiles/headless/cordis.patch.yml

Example:

- id: llm-codex-subscription
  config:
    provider: codex-subscription
    displayName: OpenAI Codex Subscription
    authFile: /absolute/private/path/codex-auth.json
    reasoningEffort: medium
    textVerbosity: low
    streamIdleTimeoutMs: 300000
    models:
      - id: gpt-5.5
        name: GPT-5.5

Configuration fields:

FieldMeaning
providerDSH route ID; after renaming it, also update agent-default-model.provider
displayNameWeb model-group display name
authFileAbsolute path to the OAuth file; the CLI must use the same --auth-file
reasoningEffortDefault reasoning effort when the caller does not specify one
textVerbosityDefault Responses text verbosity
streamIdleTimeoutMsTimeout when the provider sends no events; default 300000 ms
modelsWeb/DSH advisory catalog, not a server entitlement allowlist

There is no baseURL or endpoint setting. The fixed endpoint is a security boundary that prevents the ChatGPT bearer token from being sent to a user-configured address.

If you use both Web and headless, add the required profile-specific configuration to both files. Do not assume that one profile's patch automatically applies to another profile.

12. Verification Levels

CheckWhat it provesWhat it does not prove
dsh-codex-subs --helpThe executable CLI is installed in the current profileThe adapter has started or auth is valid
--dump-configThe bundle is in the Cordis compositionSettings defaults, OAuth, or remote entitlement
doctorLocal auth/settings/proxy presence and the cwd-manifest installation heuristicThe adapter has started, effective fallback, or remote entitlement
Web model selectorThe adapter is registered in the Web host and can list its catalogThe current model is necessarily allowed by the server
Minimal real promptThe route, auth, network, endpoint, and stream work nowLong-term stability or availability of every model

Recommended acceptance order:

dsh plugin --profile web exec dsh-codex-subs --help
dsh plugin --profile web exec dsh-codex-subs doctor
dsh --profile web --dump-config

dsh plugin --profile headless exec dsh-codex-subs --help
dsh plugin --profile headless exec dsh-codex-subs doctor
dsh --profile headless "Reply only: codex-subscription-ok"

dsh web

Finally, complete workspace selection, model selection, and the codex-web-ok request in Web.

13. Troubleshooting

SymptomCauseResolution
dsh-codex-subs: command not foundThe plugin is not installed in the current profile, or has not been builtRun pnpm run build, then run dsh plugin --profile <name> add . for the target profile
--help prints nothingThe profile still references an old build containing the early .bin symlink entry-point bugRebuild and reinstall into that profile; --help must print Usage
pnpm blocks prepare/buildThe profile's pnpm security policy has not allowed the buildAllowlist only the exact package named by pnpm, then run the installation again
OAuth callback EADDRINUSE127.0.0.1:1455 is occupiedStop the process using the port, or use login --headless
Browser login succeeds but token exchange failsThe browser and CLI use different egress paths, or the CLI is not using the correct proxySet HTTPS_PROXY/HTTP_PROXY before login, then check case conflicts and NO_PROXY
UNSUPPORTED_COUNTRY_REGION_TERRITORYThe CLI's public egress, account, or workspace does not satisfy the server's region policyCheck the official supported regions and confirm a compliant egress path. That API page does not guarantee private ChatGPT Codex backend eligibility; server/workspace policy is authoritative
Auth status: missingDifferent DSH_HOME or authFile values were usedFix DSH_HOME; for custom auth, make the plugin config and CLI --auth-file exactly match
Auth status: expiredLocal expiresAt has passed; doctor/status did not attempt refreshSend one minimal real request to trigger refresh; sign in again only if refresh fails
Login succeeds but DeepSeek still reports MISSING_CREDENTIALThe default model is still the base DeepSeek modelUpdate settings.yaml, or select Codex in the Web session model selector
no adapter registered for provider codex-subscriptionThe plugin is not installed in the current profile, Web was not restarted, or a renamed provider does not matchCheck the target profile, restart Web, and synchronize agent-default-model.provider
The dump still shows DeepSeekThe dump does not read runtime settingsInspect the settings override in doctor, check the Web model button, and send a real prompt
doctor shows not-detectedIt was run from the wrong profile/cwd, or the manifest is incompleteUse dsh plugin --profile <target> exec ... doctor; inspect web and headless separately
No Codex OAuth card appears under Settings → ModelsThis is expectedUse CLI login, then select Codex in the session composer's model selector
The Web model menu has no OpenAI Codex Subscription groupThe Web profile did not load the plugin or was not restartedRun dsh plugin --profile web add ., confirm the dump, then restart dsh web
The Web composer is disabledNo workspace is selected, or the current provider/model cannot be routedAdd a workspace first, then select a Codex model
An old session still uses the old model after changing the defaultOld sessions retain the provider/model in their logsSwitch it manually in the old session, or create a new session
Web selection succeeds but is lost after restart$DSH_HOME/settings.yaml is not writable or settings persistence failedCheck file permissions, confirm the override with doctor, then create a new session
401The adapter already refreshed automatically and retried once, but still failedSign in again; do not retry in a loop
403 / model unavailableAccount entitlement, workspace policy, or server stateChoose a model available to the account, and contact the workspace administrator or OpenAI Support
429 / usage_not_includedSubscription quota or rate limitWait for quota recovery or check the subscription plan; do not treat it as a proxy failure
Stream idle timeoutThe proxy tunnel or network sent no events for too longCheck the proxy, NO_PROXY, and network stability; raise streamIdleTimeoutMs if necessary
Image rejected before sendingThe current adapter is text-onlyCreate a text-only session; catalog spoofing cannot bypass modality validation
EADDRINUSE: 3080The Web port is occupiedUse dsh web --port 3081 or --port 0
Occasional refresh conflicts with multiple DSH processesRefresh single-flight is shared only within one process; there is no cross-process file lockStop other processes, sign in again, and avoid simultaneous refreshes from multiple processes

When diagnosing a region error, do not spoof the client ID, endpoint, or account ID, and do not copy another person's credentials. The server makes the final eligibility decision for the private Codex backend.

14. Upgrade, Uninstall, and Logout

14.1 Rebuild an Updated Local Checkout

First use your own Git workflow to update the checkout to the target revision; pnpm install does not pull new source code. Then run:

cd /absolute/path/to/dsh-codex-subs-plugin
pnpm install
pnpm run check

dsh plugin --profile web add .
dsh plugin --profile headless add .

Then restart dsh web.

14.2 Logout

Before uninstalling the plugin, run:

dsh plugin --profile web exec dsh-codex-subs logout

If the profile uses custom auth:

dsh plugin --profile web exec dsh-codex-subs logout \
  --auth-file /absolute/private/path/codex-auth.json

logout only deletes this plugin's local auth file. It does not call an OpenAI revocation endpoint and does not sign out of ChatGPT, Codex CLI, or OpenCode.

The default auth file is shared by Web and headless, so one logout removes local credentials for both profiles. Custom auth must use the same --auth-file; deleting the default file does not delete a custom file.

Deleting the local file also does not remotely invalidate a refresh token that has already been copied or leaked. If credential exposure is suspected, do not treat local logout as complete incident response. Use the appropriate OpenAI account/workspace security controls, or contact the workspace administrator and OpenAI Support.

14.3 Uninstall

First change agent-default-model in $DSH_HOME/settings.yaml to a provider/model that will remain available, or remove the override. Otherwise, new sessions will continue to point to a route that no longer exists.

Then remove the plugin from each profile:

dsh plugin --profile web remove dsh-codex-subs-plugin
dsh plugin --profile headless remove dsh-codex-subs-plugin

Restart Web.

15. Security Boundaries

  • The OAuth file contains a long-lived refresh token. Allow only the current user to read it, and never submit it to a repository, CI artifact, chat, or issue.
  • Successful login output and status show the complete auth path and may show an email address/account ID. Use only doctor for shared diagnostics, and manually review custom provider/model identifiers before sharing.
  • Do not disclose the authorization URL, device user code, complete request headers, Authorization, proxy URL, or raw error logs.
  • An online smoke test consumes ChatGPT Codex subscription usage. When the token is close to expiry or after the first 401, it may refresh, rotate, and write credentials back to the local file.
  • DSH saves session logs. Requests are also sent to the ChatGPT service and processed under the applicable workspace's retention, residency, and data-handling policies. Redirecting terminal output to /dev/null or setting store: false in the request does not mean zero retention.
  • The adapter always sends requests to https://chatgpt.com/backend-api/codex/responses and does not support a user-configured endpoint.
  • Web binds only to loopback by default. Do not expose an authenticated proxy or the Web UI to the public internet for convenience.
  • This plugin follows a private compatibility surface. Do not treat one successful smoke test as a guarantee of permanent compatibility or entitlement for every account.

16. Final Acceptance Checklist

  • Node, pnpm, and DSH versions meet the requirements, and every command uses the same DSH_HOME.
  • Proxy variables are set before login, headless, and Web startup; logs contain no proxy values or credentials.
  • pnpm run check and pnpm pack --dry-run pass.
  • The Web profile's --help and doctor work.
  • If headless is used, the headless profile's --help and doctor work.
  • doctor shows the expected local auth/settings state. When an account ID exists it appears only as present; a missing value is not by itself a failure, so use a minimal real request as the deciding check.
  • Dumps for both target profiles contain llm-codex-subscription.
  • The minimal headless request returns codex-subscription-ok.
  • dsh web prints a loopback URL and the browser can connect.
  • A workspace is selected and the composer is enabled.
  • The model menu contains OpenAI Codex Subscription, and the current model is GPT-5.5 · Medium.
  • The minimal Web request returns codex-web-ok.
  • A new session inherits GPT-5.5 · Medium.
  • Shared diagnostics contain no token, email address, account ID, Authorization header, or proxy URL.

For implementation details, upstream OpenCode evidence, and compatibility risks, see docs/opencode-codex-subscription.en.md.