cyberine
UsageRun recipes

cyberine sync: bring a repo to the current Cyberine (kpwxe, 2026-10-31)

Run recipe: cyberine sync in any repo that applied the Cyberine rules. It refreshes generated rule text (the Acceptance checks and Verification sections, when they still hold an older Cyberine wording), makes the rule files local (git rm --cached on tracked ones, staged and not committed, their ignore lines kept) and completes the Cyberine ignore block; an edited section is left alone and reported. It also rewrites every .claude/rules/cyberine-policy-*.md that Cyberine wrote and nobody edited since to the current bundled text, and updates its record in .cyberine/config.local.json (or config.json when the record is tracked); an edited policy file is left alone and reported (kvcjg, 2026-11-02). The bundled tests policy names no skill: when the repo defines its own policy on tests or CI (a rule file or a skill of its own), that policy wins wherever the two differ (owner ruling D1-A, 2026-11-04, superseding k2k0h). It is idempotent: a second run prints "already matches this Cyberine; nothing to change". --dry-run reports without writing; --hub, run in a submodule of a hub board, acts on the hub; --json prints { root, changed, items: [{ path, action, detail }] } with actions added, refreshed, untracked, gitignore, current, left-alone, absent, not-applied, removed (an untracked generated cyberine-rulings.md deleted, since rulings now reach the session through cyberine notice), tracked (a tracked generated cyberine-rulings.md named and left in place for the owner to untrack). In Claude Code the phrase "cyberine sync" or "cập nhật rule cyberine" runs it through the cyberine:init skill. A snippet or script that names only this verb keeps applying later Cyberine releases, since the templates are read at run time.

The owner's standing decisions for a repo are the owner rulings in its board ledger (owner decisions minus reverted, retired and consolidated ones; see cyberine rulings below). Since k8380 (2026-11-10) they reach a session through the capped session notice (cyberine notice, below), not through a loaded .claude/rules/cyberine-rulings.md: cyberine tasks decide --status owner and cyberine sync no longer generate that file, delete an untracked generated copy (it carries the managed hash marker) and name a tracked one in one line, leaving it in place for the owner to untrack. A generated rule or policy skill cites a ruling instead of copying it. Known limitation: the rulings are only as complete as the board ledger on this machine, since .local/tasks/ is not shared. The bundled tests agent policy (cyberine init --policies tests) and the autonomy rule's Verification paragraph name no skill of the repo: they say the repo's own test and CI policy wins where the two differ, and a repo's own generated policy skills declare that they supersede cyberine-policy-* themselves, so Cyberine never depends on the owner's tooling names (owner ruling D1-A, 2026-11-04, superseding k3n7f). cyberine init leaves test steps out of its suggested verify command when .claude/rules/cyberine-policy-tests.md is present. Since k7j1w every managed section a rule file carries (the board rule's Acceptance checks, the autonomy rule's Verification) ends in a marker line <!-- cyberine-managed sha256:<12 hex> --> over its own body: a section whose body still matches its marker was never edited by hand and is replaced on the next cyberine sync whatever template version wrote it, while an edited body no longer matches and is left alone. PAST_VERIFICATION_TEXTS and PAST_ACCEPT_SECTIONS stay only for files written before the marker and no longer grow. The invariant .cyberine/invariants/rule-template-update-path.mjs (k6x18) runs before any task in the init or accept area closes: every managed section wording in the last eight commits of templates/rules/*.md must be the current one or appear in PAST_VERIFICATION_TEXTS / PAST_ACCEPT_SECTIONS in src/core/deliveryMode.ts, so a template change without an update path cannot land silently.

Known Limitations and Deferred Work: a repo that never applied the rules gets only the ignore block and a not-applied line, because applying the rules is a consent step (cyberine init --rules); project config, fleet and the agent policies are not reconciled (k7j1w and k6x18 cover detecting edits by hash and failing on a template change without an update path).

Verify oracle (k6me5): cyberine init prints the Suggested: cyberine config set verify.command ... line, and cyberine sync sets verify.command when no layer sets it (project .cyberine/config.local.json, project .cyberine/config.json, ~/.cyberine/config.json or CYBERINE_VERIFY_CMD). The value is built only from what the repo already runs: the CI lint and test steps of .github/workflows/*.yml when there are any (the detector behind the Suggested: cyberine config set verify.command line), otherwise the test and lint scripts of package.json (npm test && npm run lint; pnpm or yarn when pnpm-lock.yaml or yarn.lock exists; the npm placeholder test script and watch-mode scripts are skipped). Nothing is invented: a repo with only a typecheck script, or no script and no CI step, gets no value and keeps DiffJudge's typecheck fallback. The value goes to .cyberine/config.local.json (gitignored), or to .cyberine/config.json with --track or when that file holds rules.applied; sync prints one line naming verify.command, its value and its source, a second run prints and writes nothing, an operator-set value is never touched, and cyberine sync skips a repo that has no .cyberine/config*.json yet. To undo it, edit or delete the verify.command key in that file. Known Limitations and Deferred Work: detection reads CI run: steps and package.json only, so a Makefile, cargo or pytest repo without a CI step gets no value; when the repo has Cyberine's tests policy rule (.claude/rules/cyberine-policy-tests.md) the test steps are left out and only the lint and typecheck steps are written, the same filter the suggestion line uses, because DiffJudge runs verify.command on every candidate unasked while that policy keeps test-suite runs on the owner's word (owner ruling D1-A).

cyberine init --rules (kfctv, 2026-09-14, superseding the previous day's --vibe-rule) writes .claude/rules/cyberine-autonomy.md and .claude/rules/cyberine-board.md at the resolved git toplevel from the bundled templates/rules/*.md (since kn7fc the board rule also carries the Acceptance checks convention, so every session in the repo knows it without the tasks skill; re-running cyberine init --rules in an existing repo adds that one section to a generated board rule that lacks it and leaves the rest of the file as it was; a hand-written rule without the init header is never touched; since k19mj the same re-run replaces the autonomy rule's Verification section when it is byte-equal to an older Cyberine wording, which banned builds, and leaves an edited one alone; cyberine init --refresh-rules does only those updates, also replaces an Acceptance checks section still byte-equal to an older wording and makes the rule files local (below), and touches nothing else; --dry-run reports them; the rule files are local unless --track is passed, so cyberine init --rules and --refresh-rules keep their .gitignore lines and run git rm --cached on any that are tracked, leaving the deletion staged for you to commit (another checkout loses the files when it pulls that commit and re-applies them with cyberine init --rules), and in Claude Code the plain phrase "cap nhat rule cyberine" or "cyberine update rules" runs it through the cyberine:init skill), substituting {{DATE}} with today's UTC date, and merges "rules": { "applied": true, "appliedAt": "<YYYY-MM-DD>", "files": [...] } into .cyberine/config.local.json (preserving every other key; --track writes .cyberine/config.json instead, kzfa2). cyberine autopilot refuses to start unless rules.applied === true in either file (config.local.json is read first, so a repo applied before kzfa2 keeps working) — the flag, or the interactive prompt below, is how a repo opts into unattended commits. Since 2026-10-05 (operator ruling) the rules are applied by default: on a real TTY with neither --rules nor --no-rules nor --yes, plain cyberine init lists the rules as one item of the init plan (default yes, see "The init plan" below), and --yes or a non-TTY applies them without asking. --no-rules leaves them unapplied and prints one hint line naming cyberine init --rules; so does "rules": { "apply": "off" } in ~/.cyberine/config.json, which turns the default off for every repo on this machine (an explicit --rules still applies them there). "rules": { "delivery": "<pr|branch|main>" } in ~/.cyberine/config.json is the delivery mode init writes when --delivery is absent, and the mode autopilot uses for a repo whose record names none. Each template file is refused per-file when already present (prints <path> already present -- leaving it. and still counts it as applied — a repo that already wrote one file by hand is not blocked from recording the other or the config field) rather than the whole operation failing when only one exists. config.json's rules.applied is authoritative for the autopilot precondition regardless of whether the rule files themselves are present — the CLI only ever writes both together, but a repo can also record the field by hand-editing .cyberine/config.local.json when a different file already plays the autonomy-grant role (this repo does exactly that in its untracked .cyberine/config.local.json, since .claude/rules/user-vibe-coding.md already covers it here, so --rules was never run against this repo). Known limitation: the bundled templates are a generic pre-authorization (the git operations the chosen delivery mode names, for a Cyberine-driven session, idiomatic-default autonomy, offer-once verification, a fixed hard floor) and generic board discipline (cyberine tasks only, evidenced done flips, owner-only backlog promotion) — a repo with its own commit/branch/CI conventions should edit the written files afterward; the flag does not read or merge any existing policy.

The init plan (k7253). cyberine init asks once. On a TTY it prints one plan with every item it may act on, each with a default switch, and a single Apply this plan? [Y/n/e] prompt: Enter accepts the defaults, n aborts and writes nothing, e toggles items by number (1,3, blank line to finish) and shows the plan again. The items: apply the Cyberine rules (default yes), write the rule blocks into AGENTS.md (default yes; one item for both blocks), install the handoff SessionStart hook (default yes), update an existing .cyberine/config.json (default no), and customise the fleet and mode by hand (default no; only this item opens the mode and worker pickers). The first-run chain of bare cyberine on a machine with no provider folds its two questions into the same plan as Set up providers now and Pin defaults for THIS project, so a new user answers once instead of three times.

cyberine init                   # TTY: one plan prompt for every pending item
cyberine init --dry-run         # print the files and the pending plan items, write nothing
cyberine init --yes </dev/null  # no prompt: every item at its default, including the handoff hook and the AGENTS.md blocks
cyberine init --no-rules --yes  # --no-rules still wins over --yes for the rules

Where answers are stored: every answer to a plan item, yes and no, goes under init.answers in .cyberine/config.local.json (gitignored, machine-local) as { "<item>": { "answer": "yes"|"no", "at": "<ISO time>", "fingerprint": "<hash>" } }. A later cyberine init shows only items that are new or changed, and when none is pending it prints no plan and asks nothing, including after a decline. The AGENTS.md item carries a fingerprint of the bundled rule templates, so a CLI upgrade that changes a template makes that item new again. A repo that already records rules.applied reads as a stored yes for the rules. --rules (or --delivery) applies the rules without consulting a stored answer, --update re-asks the customise item and skips the update item, and "rules": { "apply": "off" } in ~/.cyberine/config.json still keeps the rules out of the plan. A plan item you decline is also remembered: to change your mind, edit or delete the matching key under init.answers, or pass the explicit flag (--rules).

Known Limitations and Deferred Work: the plan holds only choices init already offered; the base steps (seeding the task manifest, the .gitignore block, pinning config.json and fleet.json on a fresh repo) are not items and run whenever init is not aborted. Under --yes the update-config item is applied (as before) but the customise item is not, because the pickers need a terminal; with no TTY and no --yes, an existing config is left alone and no answer is stored for it. The first-run chain's Pin defaults item gates only the config and fleet pinning: declining it no longer skips the rules, AGENTS.md or hook items. AGENTS.md blocks are no longer confirmed one rule at a time; the single plan item covers both.

Delivery mode (kx3ng, 2026-10-04). cyberine init --rules --delivery <pr|branch|main> decides how sessions driven under the Cyberine rules deliver, and cyberine-autonomy.md's Precedence and Delivery sections name exactly what that mode lifts. pr, the default whenever --delivery is omitted, lets a session commit on a new auto/<task-id>-<short-slug> branch, push it and open one pull request with gh pr create, and states that merging stays gated (no gh pr merge, no host-side merge, no auto-merge) and that committing on or pushing the default branch stays gated too; an interactive session lands on the default branch instead only when the operator's own message or the repo's own rules allow it, and otherwise asks once with a single yes/no picker whose recommended first option, and the default when unanswered, is to open the pull request, while an autopilot-driven session never asks. branch is the same branch pushed with no pull request. main is the explicit opt-in that names committing on the default branch, pushing it and merging into it; the name is the mode's, not the branch's: it lands on whatever branch the repo's remote names as its default (main, master, trunk, develop, ...). The default is always pr and init does not inspect the repo's CI/CD or hosting config: whether a push to the default branch builds or deploys anything depends on each repo's own CI/CD rules, not on the branch name (many teams deploy only from a differently named branch or a tag, and others protect their default branch), so landing on the default branch is something the operator chooses. A machine whose repos all land directly can say so once with "rules": { "delivery": "main" } in ~/.cyberine/config.json; a driven session in main mode still states a push that deploys an environment as a deploy before it pushes. Init prints the mode it wrote (Delivery mode: pr (the default: ...)), records it as rules.delivery beside rules.applied, and cyberine autopilot reads it from there: --dry-run shows a delivery mode row, a real run logs [autopilot] delivery mode: <mode> (...), and the claude engine's bootstrap, batch and wrap-up prompts carry a Delivery mode: <mode> line telling the driven session how to deliver (prompt version 1.6.0). cyberine doctor names the recorded mode on its Cyberine rules applied line. In pr mode the clauses that gate merging the pull request, committing on the default branch and pushing it apply only unless the operator's own message or the repo's own rules pre-authorize them, so a repo whose rules already allow commit, push and merge keeps working as that rule says. cyberine init never overwrites an existing cyberine-autonomy.md, so a repo initialized earlier keeps its old wording; to adopt the current text, delete .claude/rules/cyberine-autonomy.md and re-run cyberine init --rules.

cyberine init --rules                     # writes delivery mode pr (the default)
cyberine init --rules --delivery branch   # push an auto/<task-id>-<slug> branch, no PR, never merge
cyberine init --rules --delivery main     # explicit opt-in: commit, push and merge on the repo's default branch, whatever it is named
cyberine config set rules.delivery main --global   # every repo on this machine without its own recorded mode
cyberine autopilot --dry-run --verbose    # "delivery mode    pr (.cyberine/config.local.json rules.delivery)"

Upgrading a repo that applied the rules before delivery modes existed: plain cyberine init --rules never overwrites an existing cyberine-autonomy.md as a whole (it only refreshes a Verification section still byte-equal to an older Cyberine wording); it says the file names no delivery mode (such a file lifts commit, push and merge on every branch), records no rules.delivery, and points at the flag. Passing --delivery <mode> is the explicit upgrade: when the file carries the Written by `cyberine init --rules` header line it changes only that header and the Precedence and Delivery sections, so a local edit elsewhere in the file survives (a file from before delivery modes has no such sections and is rewritten whole, and the output says so), prints Rewrote <path>: delivery mode <old> -> <new>, leaves cyberine-board.md and every other file untouched, and is a no-op when the file already carries that mode. A file without that header was written by hand and is never rewritten (init exits 1 and says the mode was not applied). --delivery together with --no-rules, or an unknown mode, exits 2; --delivery alone applies the rules with that mode. With --harness agents the AGENTS.md block is rendered for the same mode and, as before, written only after asking.

Known Limitations and Deferred Work: the delivery mode binds the claude engine's driven sessions and any session that reads the rule file; --engine fleet never pushes, so the mode does not apply to it. Rules applied before kx3ng carry no mode until --delivery is passed, and autopilot then says not recorded and leaves delivery to the repo's own rules. Whether a pr-mode session's pull request actually opens depends on gh being installed and signed in for that remote; with no hosting remote the session keeps the pushed branch and says so. .cyberine/config.local.json is read before config.json, so a --track --delivery <mode> run also rewrites rules.delivery in an existing local record that says rules.applied, and says so. The mode is recorded only by the claude harness: --harness agents alone puts --delivery into the AGENTS.md rule text and records nothing, which it says in one line.

The Cyberine ignore block (kzfa2, 2026-09-26). A cyberine init that pins a fleet.json (not with an existing fleet.json or --no-fleet), and every init --rules, appends the missing lines of one canonical block to the repo's .gitignore, under a # Cyberine: ... header, and never twice (the last three lines only with --rules, and not with --track):

.local/
.cyberine/*.local.json
.cyberine/fleet.json
.cyberine/plans/
.cyberine/handoffs/
.cyberine/test-output.txt
.claude/rules/cyberine-autonomy.md
.claude/rules/cyberine-board.md
.claude/settings.local.json

The autonomy rule grants unattended commit/push/merge, so by default it is a per-machine opt-in: the two rule files, rules.applied (in config.local.json) and the board guard (in .claude/settings.local.json) all stay out of git, and a fresh clone keeps the ask-first default. cyberine init --rules --track is the team-shared shape: rules.applied in .cyberine/config.json, the guard in .claude/settings.json, and the rule files not ignored, so every clone inherits them. Everything else under .cyberine/ — config.json, policy.json, hooks.json, commands.json, rules/, workflows/, skills/ — is shared config and stays tracked. The convention going forward: a new personal file under .cyberine/ is named *.local.json (already covered) or is added to CYBERINE_GITIGNORE_LINES in src/commands/init.ts. Known limitation: the block is append-only — init never removes a line, never moves an existing tracked rules.applied from config.json to config.local.json, and never untracks a file already committed (that is git rm --cached, the operator's call).

The board-write guard (ke1am). The same consent also installs a Claude Code PreToolUse hook into the repo's .claude/settings.local.json (.claude/settings.json with --track; a guard already in either file counts), because cyberine-board.md's "every board write goes through cyberine tasks" was prose and nothing enforced it — an empty [] bracket only ever comes from a hand-edit. The hook is one entry, matcher Edit|Write|NotebookEdit|MultiEdit|Bash, running cyberine tasks guard: it reads the tool payload on stdin and exits 2 (block, reason handed back to the model) when the call would WRITE a board file — the manifest, TASKS_ARCHIVE.md, status.log, anything under status.d/ or journal/. Reads are always allowed, and so is a redirect whose destination is not a board path, so cat .local/tasks/ACTIVE.md > /tmp/copy.md and cp .local/tasks/ACTIVE.md /tmp/ both pass while sed -i, a > onto the board, mv/cp ONTO it, tee, and rm are refused with the rule quoted. An existing settings.json is MERGED, never rewritten: other hooks, permissions and env survive, re-running is a no-op, and a settings.json that is not readable JSON is reported and left untouched rather than replaced.

cyberine init --rules      # writes the rules AND the guard hook
echo '{"tool_name":"Write","tool_input":{"file_path":".local/tasks/ACTIVE.md"}}' | cyberine tasks guard; echo $?   # 2

Known Limitations and Deferred Work. The Bash arm matches write INTENT plus a board path rather than parsing the shell, so an exotic route (a python one-liner opening the file, an unrecognised verb) still gets through, and a command it refuses may occasionally be innocent — rephrase it rather than fighting it. The guard is per-repo: it protects the repo whose settings.json holds it, and only inside Claude Code. The hook calls the cyberine on PATH, so a repo initialised by one install follows that install's version of the rule.

The board banner at session start (kzyxj). A session learns the board exists from one banner line at start: the status --summary counts, a Resume: line naming the first [doing] (or unblocked [todo]) task, and a recurring-due line when one is due. It stays silent in a repo with no board or a board with no task blocks. With the plugin installed it comes from the plugin's own SessionStart hook (see "Plugin hooks" above). Without the plugin, add it by hand to the one repo that wants it, either in the tracked .claude/settings.json (the whole team gets it) or in the gitignored .claude/settings.local.json (only this machine), and drop it again once the plugin is installed:

{
  "hooks": {
    "SessionStart": [
      { "matcher": "startup|resume|clear|compact",
        "hooks": [{ "type": "command", "command": "cyberine-tasks-banner", "timeout": 5 }] }
    ]
  }
}

cyberine-tasks-banner (bin/cyberine-tasks-banner.mjs, a package bin) is a plain-Node launcher: it caches the rendered banner under $CYBERINE_HOME/cache/tasks-banner/, keyed on the session cwd, the local date, the package version and the size and mtime of every board input (all three manifest candidates, status.log, every status.d/ entry, TASKS_ARCHIVE.md), and only boots the CLI (cyberine tasks banner --cwd <dir> --json) on a miss. Measured on this repo: a miss 1.85 s, a hit 0.12 s, both inside the 5 s hook budget. From a checkout that is not npm-linked, point the command at the file instead: node "$CLAUDE_PROJECT_DIR/bin/cyberine-tasks-banner.mjs".

cyberine tasks banner                          # print the banner for this repo (no cache)
cyberine tasks banner --cwd ../other --json    # {"banner": "...", "inputs": [...]}, what the launcher caches
echo '{"cwd":"'"$PWD"'"}' | cyberine-tasks-banner   # exactly what the hook runs
CYBERINE_TASKS_BANNER_NO_CACHE=1 cyberine-tasks-banner   # force a re-render

Known Limitations and Deferred Work. cyberine init does not offer to write the hook yet; add it by hand. The cache key does not cover the journal (the banner does not read it) or the claim-lease clock inside a day, so a lease that expires mid-day shows on the next board write or the next day. The banner shows the same counts as cyberine tasks status --summary.

Recent done (knpg5). A task closed and archived out of the live manifest still counts as work that exists, so the banner adds ONE line after Resume:, recent done (7d): k1 TITLE, k2 TITLE (+N more) (cyberine tasks status for all): the three newest, each title clipped to 40 characters, newest first. cyberine tasks status (the full view, not --summary) adds a [recently done (7d, archived included)] section listing every one as <id> [done <date>] <title>. Both read the completed: YYYY-MM-DD date of the [done] blocks in TASKS_ARCHIVE.md and of any [done] block still in the manifest, and neither prints anything when nothing was completed inside the window. A session scoped to a submodule sees only the tasks whose path: is inside it, the same filter its todo and doing tasks use; --all shows every task. A session asked "does X exist" reads this section before answering no, then cyberine tasks corpus --recent N for the body.

cyberine tasks status | sed -n '/recently done/,/^$/p'
cyberine tasks banner | grep '^recent done'

Known Limitations and Deferred Work (recent done). The 7-day window is fixed, not configurable. A done task whose block carries no completed: date (older archives, or a flip not yet reconciled and so not yet baked into the header) is never listed. cyberine tasks status --summary and --json do not carry the section.

Owner tasks (kvk9g). A task with exec: manual is an Owner task: only the operator can do it (docs/tasks-manifest.md section 15), and the count follows exec: manual, not the Owner: title prefix. cyberine tasks status adds an [owner: N actionable, M waiting] section listing each actionable one as <id> <title>, and the [cyberine tasks] summary line, which is also the banner's first line and stays ONE line, appends · N owner (M waiting) only when either count is non-zero. Actionable means a todo task with every dep satisfied (an archived dep counts as done, the same rule as cyberine tasks next) and no blocker:; waiting means a todo task held by an unmet dep or a blocker. Done, doing, backlog, forbidden, duplicate (duplicate-of:) and archived tasks are never counted. cyberine tasks status --json (still board-status/1) adds counts.ownerActionable, counts.ownerWaiting and a per-task exec field. A session scoped to a submodule counts only the tasks whose path: is inside it; --all counts every task.

cyberine tasks status | sed -n '/^\[owner:/,/^$/p'
cyberine tasks banner | head -1
cyberine tasks status --json | jq .counts

Known Limitations and Deferred Work (owner tasks). The counts read the exec: field only: a manual task filed without exec: manual is invisible to them, and an exec: manual task that is not titled Owner: is still counted. A task claimed with doing leaves the count, since it is no longer waiting on anyone.

Blocker owner (kdybx). A blocker that waits on someone outside the run says who, so the right person sees it. waits-on: is a manifest field whose value is user, repo:<path>, session:<name>, upstream:<vendor> (a vendor release or rollout, for example upstream:anthropic) or event:<what> (a natural event no one can hurry, for example event:seat quota exhausted; k0g0a). cyberine tasks status block <id> "<reason>" --waits-on <value> writes it beside blocker:, add and set take --waits-on <value>|- (- removes it), status unblock clears it, and cyberine tasks status, cyberine next and the --json forms print it beside every blocker (waitsOn is a string or null on each task, and on each blocked entry of next --json). cyberine tasks doctor section 14 lists a blocked task that has no waits-on: and whose blocker text names no owner; it is a warning and never fails the report.

cyberine tasks status block <id> "needs the staging API key" --waits-on user
cyberine tasks status block <id> "waits for the sdk release" --waits-on repo:/Users/me/WORK/AI/sdk
cyberine tasks set <id> --waits-on upstream:anthropic
cyberine tasks set <id> --waits-on "event:seat quota exhausted"
cyberine tasks set <id> --waits-on session:dot-claude_312
cyberine tasks set <id> --waits-on -
cyberine tasks status --json | jq '.tasks[] | select(.waitsOn != null) | {id, blocker, waitsOn}'
cyberine tasks doctor | sed -n '/^14)/,$p'

Known Limitations and Deferred Work (blocker owner). Any other value shape is refused with a message and nothing is written. Re-blocking a task without --waits-on keeps the owner already recorded, so pass the flag again when the owner changed. The doctor warning is advisory: it skips a blocker whose text already contains a word such as user, owner, operator, human, repo: or session:, and it does not judge whether the named owner exists or is alive. Blockers that Cyberine writes itself (autopilot and the autonomy loop on a failed verify) carry no waits-on: yet. An older board tool that does not know the field reads it as an unknown field and warns without failing.

Known Limitations and Deferred Work — detection and the detected preset

Detection is evidence-based and read-only: it looks at env vars, config/credential FILES, the macOS Keychain (claude only, and only when the platform really is darwin), PATH, and the seat token files declared in the global fleet.json. It never makes a network call, never opens a session and never reads a credential VALUE into anything it prints. That draws a hard boundary, and these setups fall outside it — each one is a case where cyberine init will UNDER-enable and you finish by hand with cyberine fleet enable <provider>:

  • A credential that only exists inside a login the CLI keeps to itself. Muse stores its real token in the OS keychain and keeps only a login-STATE record in auth.json; delete that record while the keychain token is still live and muse keeps working while detection reports not-logged-in. Cursor Agent is detected from the mere presence of ~/.cursor/, which is coarse in the other direction. Neither is closable without reading a credential store this code deliberately does not touch.
  • A credential that is only valid, not only present. Every check is presence, never validity: an expired, revoked, typo'd or unfunded key detects as usable and fails at dispatch instead. A live check would mean spending a call per worker on every init, fleet list and setup --status.
  • A credential injected at RUN time only. A key exported by a shell wrapper, a CI secret, a direnv block, a systemd unit or a secret manager that is not in the environment when init runs is invisible to it. config.ts folds shell env, the config.json layers, $CYBERINE_HOME/.env and ./.env into the environment first, so anything in those four is seen; nothing else is.
  • A credential on a remote machine. Detection describes THIS host. A fleet.json committed from a laptop and used on a runner describes the laptop.
  • A provider behind a gateway or proxy that authenticates for you. loop-claude-build / loop-gpt-build are enabled by any explicit GATEWAY_* / LOOP_MODEL_* var, which is an opt-in signal and not proof the gateway is reachable; a gateway that needs no env var at all is not detected.
  • Bedrock and Vertex ambient credentials. Both accept an instance profile, ECS task role or gcloud ADC that no env var names. Detection requires the explicit opt-in (BEDROCK_PROFILE / AWS_PROFILE / an explicit key pair; VERTEX_PROJECT_ID), so an ambient-credential host under-enables.
  • opencode's per-provider credentials are fail-open, not detected. An unreadable auth.json or an unrecognized provider prefix reports usable rather than blocking a working custom route (hl-t693), so an opencode worker can be enabled and still fail to authenticate at dispatch.
  • Several accounts for the SAME worker are only modelled for claude. Seats give one claude-<seat> lane per declared seat; no other worker has a seat concept, so two ChatGPT accounts or two DeepSeek keys collapse into one row. k1fpd tracks the related case where a seat and the keychain login are the same account and nothing warns.
  • A seat token's MODE is reported, not enforced. A 0644 seat token is listed with mode 644 and still enabled; nothing refuses it.
  • A retired worker is never detected-usable, whatever credential is present — RETIRED_WORKERS wins. Un-retire it in src/core/workerProfiles.ts if the account is funded again.

Reproduce any of these against the real code with a throwaway script driving detectWorker / detectFleet from src/core/workerDetection.ts: every input (env, platform, home, PATH, $CYBERINE_HOME, cwd) is an injectable field on DetectionContext, so a Linux-without-a-keychain or env-credentials-only machine can be described without a container and without touching a real credential file.