/li:cli-fingerprint
CLI detection runtime that feeds the portability shim. Foundational — every other skill's degradation decision depends on knowing which CLI is currently running.
P1 fix T2 (eng-review): Phase B sub-step 0. Without reliable detection the shim layer is guessed.
Detected IDs (the enum): claude-code, codex, copilot, copilot-cli, copilot-app, copilot-vscode, copilot-cloud, copilot-coding-agent, cursor, gemini, opencode, droid. For capability-tier lookups the ID is normalized onto the 8 rows of lib/cli-tiers.yaml via cli_tier_normalize (lib/cli-tiers.sh): copilot-cli/copilot-app → copilot; anything unrecognized → other. That mapping is what lets /li:welcome's honest-tier banner reach every row.
When to use
- Session start (auto-run by
/helpand other entry skills) - Operator unsure which CLI Claude / agent is actually running in
- Debugging shim behavior ("why did this skill use degraded path?")
- After CLI install/update — verify detection still picks the right one
When NOT to use
- Inside a skill that already received its CLI ID from session cache
- Test fixtures with mock CLI ID (use
LINTEL_CLI=testinstead)
Inputs
- Optional
--force-redetect— ignore session cache, re-run all detection steps - Optional
--declare <cli-id>— operator-side manual declaration (writes to~/.lintel/cli-id.txt) - Optional
--verbose— print the cascade evaluation step-by-step
Workflow
Check explicit env var first.
if [ -n "${LINTEL_CLI:-}" ]; then return "$LINTEL_CLI" fiThis is the highest-priority signal — operator pinned via shell init or per-invocation
LINTEL_CLI=codex command.Process inspection.
- Check
$0/process.argv0for known binary patterns:claude-code,claude→claude-codecodex,codex exec→codexcopilot→copilot-cli; the retiredgh copilotextension is not evidence of the current agent CLI- GitHub Copilot App native bundle paths →
copilot-app cursor,cursor-agent→cursorgemini→geminiopencode→opencodedroid→droid
- Check parent process tree (1 level up) for the same patterns. Some CLIs spawn shells that obscure $0.
- Check
Tool-availability probe.
- Check for CLI-specific env vars:
CLAUDE_CODE_VERSION(orANTHROPIC_*) →claude-codeCODEX_*→codexCOPILOT_CLIor an active Copilot process →copilot-cli;GH_TOKENalone is not host evidence and must never be read or printedCURSOR_TRACE_ID(or otherCURSOR_*) →cursorGEMINI_CLI/GEMINI_*→geminiOPENCODE_*→opencodeFACTORY_*→droid
- Check for CLI-specific filesystem markers (all weak signals — could be stale installs):
~/.claude/config.jsonexists → suggestsclaude-code~/.codex/config.tomlexists → suggestscodex~/.cursor/exists → suggestscursor~/.gemini/settings.jsonexists → suggestsgemini~/.config/opencode/exists → suggestsopencode~/.factory/exists → suggestsdroid
- Check for CLI-specific env vars:
Operator-declared fallback.
- Read
~/.lintel/cli-id.txtif exists - This is operator-set via
/li:cli-fingerprint --declare <cli-id>
- Read
Refuse + ask.
- If all detection steps fail: print:
Could not detect CLI. Lintel needs to know which CLI it's running in to apply the correct shim behavior. Set LINTEL_CLI env var: export LINTEL_CLI=claude-code # or: codex / copilot-cli / copilot-app / cursor / gemini / opencode / droid Or declare via skill: /li:cli-fingerprint --declare <cli-id> - Exit 1.
- If all detection steps fail: print:
Cache result.
- Write detected CLI to
~/.lintel/sessions/$SESSION_ID/cli-id.txt - Subsequent skill invocations read cache instead of re-running detection.
- Write detected CLI to
Report.
Report format
CLI fingerprint: claude-code
Detection cascade:
Step 1 (env var LINTEL_CLI): not set
Step 2 (process inspection): match — process.argv0 contains "claude-code"
Step 3 (tool probe): skipped (matched at step 2)
Step 4 (declared fallback): skipped
Step 5 (refuse): skipped
Cached to: ~/.lintel/sessions/47821-1716926400/cli-id.txt
TTL: session
Override: LINTEL_CLI=<other> in env, or /li:cli-fingerprint --declare <other>
Shim behavior for this CLI:
AskUserQuestion: native
Agent tool: native (Task tool)
Browser tool: full
MCP: full
Compliance integration
- CLI ID is not sensitive — Layer 2 rules don't apply.
- Audit log entry per detection event:
.claude/runtime/audit/cli-detect.jsonl. Helps debug "why is this skill using degraded path?". - Operator-declared override is logged with operator reason if provided.
Failure modes
- Detection cascade falls through to step 5: refuse + clear instructions. Don't guess.
- Env var contains invalid CLI ID: validate against the enum (
claude-code,codex,copilot,copilot-cli,copilot-app,copilot-vscode,copilot-cloud,copilot-coding-agent,cursor,gemini,opencode,droid); reject unknown values with error. - Cache file unreadable / corrupted: delete cache + re-run detection. Should be transparent to operator.
- Process inspection finds multiple matches (claude-code + codex both in process tree): prefer the one with shorter PID distance to current process. If tied, treat detection as uncertain and ask for or use the explicit host declaration; never prefer a vendor by popularity.
- Conflicting signals (env var says codex, process says claude-code): env var wins. Log conflict to audit.
Examples
Standard session-start:
> /li:cli-fingerprint
✓ Detected: claude-code (via process inspection)
Cached to session.
Force redetect after CLI upgrade:
> /li:cli-fingerprint --force-redetect --verbose
[Step-by-step cascade printed]
✓ Detected: codex (env var LINTEL_CLI=codex)
Operator declares manually:
> /li:cli-fingerprint --declare copilot-app
Wrote ~/.lintel/cli-id.txt = copilot-app
Subsequent detections will use this declared value (step 4) if no env var or process match.
Refusal:
> /li:cli-fingerprint
✗ Could not detect CLI.
[Instructions printed]
See also
lib/cli-tiers.sh—cli_tier_normalizemaps these IDs onto the 8lib/cli-tiers.yamlrows for the honest-tier lookup (/li:welcome)CLI-SUPPORT-V2-SCHEMA.md— schema this skill's output feeds~/.lintel/config.yaml— operator overrides per-skill cli_supportverify.sh --portability— schema validation- Phase B design — full shim runtime that consumes detection