**Title:** Concurrent sessions leak mode state (ultrawork/autopilot/ralph) across sessions

## Summary
When multiple OMC-powered Claude Code sessions run concurrently (e.g. different terminal tabs), the persistent mode state from one session can bleed into another session. A second session may pick up an autopilot or ultrawork reinforcement it never activated, and ending one session sometimes tears down the active mode state of an unrelated session.

## Steps to Reproduce
1. Start a Claude Code session with OMC, activate an autopilot or ultrawork task.
2. In a *separate* terminal, start another Claude Code session in the same project directory.
3. Observe that the second session starts receiving mode-reinforcement messages (or other mode-specific behavior) even though it never activated that mode.
4. End the first session; notice that the second session’s active mode state is sometimes also cleaned up, causing it to stop working.

## Suspected Cause
The session-isolation checks for persistent mode states appear to use a pattern like:
`if (state.session_id && sessionId && state.session_id !== sessionId) return;`

This gate only blocks access when **both** a stored `session_id` and a current `sessionId` are present *and* they differ. If either is `undefined`, the check is effectively skipped, and any session can see and act on another session’s state. State files can legitimately have `undefined` `session_id` (legacy states or states created before the field existed), but right now that opens the door to cross-session leakage.

## Expected Behavior
- A mode state belonging to session X should only affect session X.
- If a stored state has no `session_id`, it should be considered unowned and should not match any specific requesting session (treat it as an exact mismatch when the caller provides an id).
- Session-end cleanup (`cleanupModeStates`) must only remove states that belong to the ending session, not all active states in the project.
- Session restore (startup) should only restore a mode if its stored state matches the current session.

## Areas That Need Attention
This affects every hook that reads a mode-state file and compares its `session_id` with the caller’s session:
- **ultrawork** mode (the `shouldReinforceUltrawork` check, and any usage that evaluates whether ultrawork is active for a session)
- **ralph** persistent mode (the `checkRalphLoop` function that decides whether to block and reinforce)
- **autopilot** mode (the `checkAutopilot` function deciding whether to enforce continuation)
- **session-start** restore logic that re-issues autopilot/ultrawork restore prompts
- **session-end** cleanup that removes active mode state files (`cleanupModeStates` currently takes no session ID and wipes all active states unconditionally)

The fix should switch to strict equality matching (`stored_session_id === caller_session_id`) for all these checks, and update `cleanupModeStates` to accept an optional session ID so it can scope its deletions to the current session (while still cleaning legacy states that have no `session_id`).

## Impact
Without this fix, users running multiple OMC sessions in parallel get unpredictable mode activation, incorrect mode teardowns, and confusing CLI behaviour. The project advertises parallel execution capabilities, so concurrent-session correctness is important.

fix(state): prevent ultrawork state leak across concurrent sessions (#269) (#271)


**Ticket: Team API cleanup bypasses runtime shutdown, leaking resources**

The `omc team api cleanup` operation currently calls `teamCleanup` directly, deleting team state files without invoking the proper runtime shutdown sequence (`shutdownTeamV2` / `shutdownTeam`). This leaves dangling tmux sessions, worker processes, and leader panes, and it also bypasses safety gates such as `cleanup_requires_all_workers_inactive`.

The cleanup handler should be changed to route through the appropriate runtime shutdown path when a team config exists:
- If the team uses the v2 runtime (detected by the presence of a `workers` array in the team config), call `shutdownTeamV2`.
- If the team uses the legacy runtime (detected by `agentTypes` in the config), call `shutdownTeam` with the appropriate session name and pane ID extracted from the config.
- If no config file exists at all (orphaned state), fall back to the existing raw `teamCleanup` behavior to remove residual state.

Additionally, when a shutdown function throws (for example, because pending tasks or active workers violate the governance gate), the API response must reflect this failure. Currently, a thrown error after a direct `teamCleanup` call would still leave the response as `ok: true`. The cleanup handler must propagate shutdown failures as `{ ok: false, error: { code: 'operation_failed', message: ... } }` and must not delete team state when the shutdown gate blocks the operation.

Implement this by introducing a dispatch helper that reads the team config, routes to the correct shutdown function, and falls back to raw cleanup only when no config is present or when the config structure doesn’t match either runtime. Ensure the `cleanup` case in `executeTeamApiOperation` calls this helper instead of `teamCleanup` directly.

fix(team): route api cleanup through shutdown


**Title:** Windows: `claude` binary not found when installed via npm (`.cmd` wrapper)

**Description**
On Windows, `oh-my-claudecode` cannot launch the Claude CLI even when `claude` is installed globally via npm and is present on `PATH`. The error is "claude: command not found" or a similar `ENOENT` when OMC tries to execute `claude` through Node.js `child_process` calls.

The root cause is that npm on Windows installs CLI tools as `.cmd` wrapper scripts (e.g., `claude.cmd`) rather than direct executables. Node's `execFileSync` and `spawnSync` do **not** resolve `.cmd` wrappers automatically when run without `shell: true`. Several places inside OMC that invoke `claude` are missing this Windows-specific resolution.

**Affected behaviors**
- `isClaudeAvailable()` always returns `false` on Windows.
- Any `runClaude` launch (direct, inside tmux, outside tmux) fails.
- The `autoresearch` setup session (print mode) that spawns `claude -p …` fails.

**Expected behavior**
Every internal `execFileSync` / `spawnSync` call that targets the `claude` binary must use `shell: true` when `process.platform === 'win32'`. On Unix platforms the behaviour must remain unchanged—no `shell: true`, no extra overhead.

**Constraints**
1. **Minimal change** – do not introduce a shared binary resolver abstraction or refactor the existing launcher logic. Just add the platform guard at the concrete call sites.
2. **Keep Unix untouched** – ensure no `shell` option is set on `darwin`/`linux`.
3. **No other side-effects** – the only thing that changes is how the child process is spawned on Windows; all other options (`cwd`, `stdio`, `encoding`, etc.) stay as they are.

**Acceptance criteria**
- On Windows, `isClaudeAvailable()` returns `true` when `claude` is installed via npm and present in `PATH`.
- `runClaude` (any policy: direct, inside tmux, outside tmux) successfully starts a Claude session.
- The `autoresearch setup` workflow is able to invoke `claude -p …` and receive its output.
- On non-Windows platforms there is zero behavioural or performance change.

Ensure Windows can launch npm-installed Claude CLI


**Title:** Native Windows (win32) doesn't detect psmux / tmux.cmd, leading to false warning and broken tmux features

**Description:**
When running OMC on native Windows with psmux (or any tmux‑compatible wrapper that exposes `tmux.cmd`) installed, the win32‑specific warning still fires and any tmux‑dependent orchestration features fail because the tool cannot detect the tmux binary properly. The current detection for Windows either uses a direct spawn of `tmux` (which doesn’t account for `.cmd`/`.bat` extensions and won’t work without shell resolution) or duplicates its own availability check in the warning module instead of reusing the centralized tmux‑utility logic.

**What needs to change (high‑level):**

1. **Centralise tmux availability check**
   The win32‑warning module should call into the shared detection function already exported by the tmux‑utils module, rather than maintaining its own `hasTmuxBinary` helper that performs a separate check.

2. **Correct Windows detection in the shared utility**
   The shared availability check must handle native Windows properly:
   - Use the system’s search mechanism (e.g., `where tmux`) to locate the binary, respecting `COMSPEC` for shell resolution.
   - When the resolved binary is a `.cmd` or `.bat` file (as with psmux), execute it via `cmd /d /s /c "…"` with the `-V` version flag to confirm it works, instead of trying to run it directly.
   - Fall back gracefully if no tmux‑compatible binary is found.

3. **Preserve existing API surface**
   The tmux‑utils module’s public exports (`tmuxExec`, `tmuxShell`, `tmuxSpawn`, `tmuxExecAsync`, `tmuxShellAsync`, `resolveLaunchPolicy`, etc.) must not change their signatures or break callers. The change should only extend detection logic and realign the warning module’s dependency.

**Acceptance behaviour:**
- On win32 *without* a tmux‑compatible binary, the warning still appears as before.
- On win32 *with* psmux (or any `tmux.cmd` on `PATH`) correctly installed, the warning is suppressed and `resolveLaunchPolicy` / `isTmuxAvailable` return success for tmux‑dependent paths.
- Non‑Windows platforms are unchanged.

**Additional note:**
Recent refactoring may have altered how `tmuxExecAsync` / `tmuxShellAsync` obtain their promisified exec helpers; make sure that any changes do not regress the behaviour of those exported functions, and that any internal helper needed for Windows detection does not affect their existing usage.

fix(cli): restore tmux-utils API compatibility for #2441 (#2442)


## Summary
The HUD model element sometimes fails to extract version numbers from the Claude model name, showing e.g. just "Opus" instead of "Opus 4.6" even when the model is `claude-opus-4-6-*.` The root issue is that the name fed into `extractVersion` / `formatModelName` can be the `display_name` string (like `"Opus 4.6"`) which the current regex does not match, or that the `model.id` is the richer hyphenated form and should be preferred as the source.

## Desired behavior
1. `getModelName` should prefer `model.id` (e.g. `claude-opus-4-6-20260205`) over `model.display_name` when both are available, because the ID reliably contains a programmatic version token.
2. `extractVersion` in the model formatting code should handle both the hyphenated ID format (`opus-4-6`, `sonnet-4-5`) **and** the human display‑name format that may already contain a version (e.g. `"Opus 4.6"`, `"Sonnet 4.5"`). If a display name with a version is encountered, it should still be parsed correctly.
3. The `versioned` model format (one of the `ModelFormat` options) should continue to work correctly for all valid model identifiers, falling back to the short class name when no version pattern is found.

## Constraints / edge cases
- Older model IDs without a numeric version (e.g. `claude-3-opus-20240229`) should fall back to the short class name (`"Opus"`).
- The full‑format output should still be the raw ID string.
- The change must not break the `renderModel` path for any existing model ID; a missing `display_name` should still fall back to `id`, and vice‑versa (though `id` is now the priority).

fix(hud): prefer model.id over display_name and fix test assertions


**Title:** Task claiming race condition allows duplicate assignments

**Summary**
Two workers calling `findNextTask` concurrently can claim the same task because the current sleep+jitter+re‑verify strategy is not atomic. This causes duplicate execution and potential state corruption.

**Expected behaviour / acceptance criteria**

1. **Atomic claiming** – No two workers may ever successfully claim the same pending task simultaneously. The implementation must use OS‑level atomicity (e.g., `O_CREAT|O_EXCL` file creation) rather than timing‑based heuristics.
2. **Exclusive lock files** – Each task claim attempt creates a unique lock file; only one worker can create it. If creation fails (because another worker already holds the lock) the task is skipped and the worker continues to the next task.
3. **Cleanup** – Lock files must be removed after a successful claim, when a lock is released, or if the critical section throws.
4. **Stale‑lock detection** – Locks left behind by crashed or killed workers must be automatically reaped when:
   - The lock file is older than a configurable threshold (default 30 s), **and**
   - The owning PID recorded in the lock is dead (confirmed via `kill(pid, 0)`).
5. **Never reap a live lock** – A lock belonging to a still‑running process must never be reaped, regardless of its age.
6. **Malformed lock files** – A lock file that cannot be parsed is treated as stale once it exceeds the age threshold, and should be reaped.
7. **Task‑update hardening** – `updateTask` must attempt to acquire the same lock before writing to avoid lost updates, but fall back to an unlocked write with a console warning if the lock is unavailable (preserving backward compatibility). It must also support an explicit opt‑out of locking.
8. **Non‑interference** – Lock files must not appear in `listTaskIds` results, nor interfere with reading existing task or failure sidecar files.
9. **Sequential double‑claim prevention** – Even without concurrency, after the first call claims a task and sets its status to `in_progress`, a second `findNextTask` call from the same worker must skip that task (return `null`).

**Location / scope**
The changes should be confined to the task file operations module (`src/team/task-file-ops.ts`) and its existing utilities without external dependencies.

**Notes for implementation**
- The lock payload should contain the PID, worker name, and timestamp so stale detection can identify the owner.
- Any helper for “execute under lock” must guarantee release in a `finally` block.
- All existing callers of `updateTask` must continue to work without modification.

fix(team): replace sleep+jitter task claiming with O_EXCL lock files


# Implementation Request: `omc team --new-window` not honored

The `omc team` start command currently ignores the `--new-window` flag.
When a user runs e.g.
`omc team 2:codex "review auth flow" --new-window`
the team should be spawned in a new terminal (tmux) window, but it launches in the current window instead.

**Expected behavior:**
1. `omc team [N:agent-type[:role]] --new-window "<task>"` must open the team in a new tmux window.
2. The flag should work when combined with `--json` and with comma-separated agent-type specs, e.g.
   `omc team 1:codex,1:gemini --new-window --json "compare"`.
3. The `--new-window` flag must be forwarded to the underlying team-start logic (both v1 and v2 code paths) so the new window is actually created.
4. Update the `omc team` start command help text to document `--new-window` and include a usage example.

**Acceptance criteria:**
- Passing `--new-window` to `omc team start` causes a separate tmux window to be used for the team session.
- The flag is correctly parsed alongside other flags and arguments, including comma-separated agent lists.
- The help output (`omc team --help`) shows the `--new-window` flag and an example.

fix(team): parse --new-window CLI flag in parseTeamArgs (#1485)


**Prevent empty hook injections from stalling post-agent turns**

Some hook handlers use empty strings (`""` or whitespace-only) as internal sentinels for `message`, `systemMessage`, and `hookSpecificOutput.additionalContext`. When the bridge CLI serializes the hook output to JSON on stdout, these empty strings are emitted verbatim. Claude Code then interprets them as context to inject on the next API turn, creating blank injected messages that confuse the model. This is especially problematic after a Task/Agent completion, when the model is deciding whether to continue and can stall or produce unnecessary extra turns.

We need to sanitize the hook output right at the final serialization boundary—just before `console.log(JSON.stringify(...))` in the bridge’s main entry point—so that:

- Top-level `message` is dropped if empty/whitespace-only.
- Top-level `systemMessage` is dropped if empty/whitespace-only.
- `additionalContext` inside `hookSpecificOutput` is dropped if empty/whitespace-only; if that leaves the `hookSpecificOutput` object empty, remove the whole `hookSpecificOutput` field.
- All other fields (e.g., `continue`, `reason`, `permissionDecision`, `permissionDecisionReason`) must be preserved unchanged.
- Non‑string values for these text fields are left alone (they aren’t injectable text).

**Constraint**: Keep the fix scoped to hook serialization—do not rewrite individual hook producers. That gives us a single defense covering all current and future hook paths.

Prevent empty hook injections from stalling post-agent turns (#2219)


**Feature request: OpenClaw integration should support CLI-based (command) gateways, not just HTTP**

### Problem
Right now the OpenClaw gateway system only understands HTTP endpoints. When a hook fires (session start, stop, tool use, etc.), the dispatcher always constructs an HTTP POST payload and sends it to a URL. This works for HTTP-based OpenClaw deployments, but several popular setups—including the official Clawdbot/OpenClaw—use a WebSocket + CLI interface where the canonical way to deliver instructions is by running a shell command like `openclaw agent --message "..." --deliver`. Sending an HTTP POST to such a deployment fails silently (returns 405), and there’s no way to configure the system to invoke a CLI instead of an HTTP call.

### Desired behavior
Add support for a new `"command"` gateway type in the `omc_config.openclaw.json` `gateways` block. When a hook fires and the resolved gateway has `type: "command"`, the system should shell-execute the configured command template rather than making an HTTP request.

#### Gateway configuration shape
Inside `gateways`:
```json
"my-cli-gateway": {
  "type": "command",
  "command": "openclaw agent --message {{instruction}} --deliver",
  "timeout": 5000
}
```
- `type` must be the literal string `"command"`. (HTTP gateways may omit `type`, or use `"http"`, for backward compatibility.)
- `command` is a required template string. It supports `{{variable}}` placeholders that are filled from the hook context (session ID, project path, instruction text, event name, etc.).
- `timeout` is an optional per-command timeout in milliseconds. If absent, a sensible default (e.g., 10000 ms) should be used.

#### Variable interpolation and safety
All variable values interpolated into the command must be **shell-escaped** so that values containing spaces, quotes, or shell metacharacters don’t break the command or create injection vulnerabilities. Escaping should use single-quote wrapping with proper handling of internal single quotes (the standard `'it'\''s'` pattern).

Template placeholders that reference undefined variables should be left as-is in the resulting command string (e.g., `{{missing}}` stays `{{missing}}`), rather than being removed or causing an error.

#### Execution model
The command should be executed via `sh -c <interpolated-command>`, inheriting the current process environment. Execution should be non-blocking from the hook’s perspective—the system already handles failures gracefully and should continue doing so, returning an `OpenClawResult` (with `success: false` and an error message) when the command fails or times out. Normal "command exited with non-zero code" errors should surface via the error string, and any non-Error exceptions should produce a safe fallback error message.

#### Routing
The orchestrator (`wakeOpenClaw`) should inspect the resolved gateway config and dispatch cleanly:
- If the gateway is a command type, call the shell execution path (passing the full set of context variables) and **skip** HTTP payload construction and the HTTP dispatch routine entirely. The interpolated `instruction` string should be available as one of the variables (`{{instruction}}`) so the command template can use it directly.
- If the gateway is an HTTP type (or has no `type` field), continue with the existing HTTP POST flow, including building the JSON payload object.

#### Config validation
`resolveGateway` should accept command gateways that have a non-empty `command` field. It should reject (return null) for a command gateway whose `command` field is missing or an empty string, just as it rejects an HTTP gateway without a `url`. Existing configurations without a `type` field must continue to work without changes.

### Additional request: tmux tail context for stop/session-end events
For `stop` and `session-end` hook events, automatically capture the last few lines (e.g., 15) of the current tmux pane’s content as a `tmuxTail` field, and include it in the context provided to the gateway:
- For HTTP gateways: add `tmuxTail` to the `OpenClawPayload` and `OpenClawContext`.
- For command gateways: include `tmuxTail` as one of the template variables.
- This should be best-effort and non-blocking: if the process is not inside tmux, or the pane capture fails for any reason, the field should simply be absent/undefined rather than causing the wake attempt to throw or fail. It should not interfere with the gateway dispatch.

### Why this matters
Without this, teams using CLI-based OpenClaw deployments (Clawdbot, etc.) cannot integrate with the hook system at all. The addition of tmux tail context also gives downstream agents useful situational awareness when a session stops or ends. The change should be fully backward-compatible with all existing HTTP gateway configurations.

feat(openclaw): add CLI command gateway type with tmux tail context (#1087)


**Title:** Clean up team workers when a Claude Code session ends

**Problem:**
After a Claude Code session exits, any team workers that were started by that session (tmux panes/windows for team, swarm, ultrapilot) are left running. This leaves orphaned tmux resources that never get cleaned up, especially when using multi-agent modes.

**Where:**
The `SessionEnd` hook (`processSessionEnd`) currently does not shut down any team workers. It should perform a best-effort cleanup for all teams owned by the ending session.

**Behavior required:**

- For each team the ending session owns, shut down its workers.
- Determine ownership from two sources:
  - The session’s mode state (`readModeState('team', ...)`) to get the team name the session is actively running.
  - Team manifests under `.omc/state/team/` — only shut down a team if its manifest’s `leader.session_id` matches the ending session ID. This must never shut down teams owned by other concurrent sessions.
- Handle both runtime variants:
  - **runtime-v2 teams** (configuration has a `workers` array) — force-stop via `shutdownTeamV2` with zero timeout.
  - **legacy teams** (configuration has `agentTypes`) — shut down via the legacy `shutdownTeam`, passing the tmux session name, leader pane ID, and `tmuxOwnsWindow` flag from the config.
- If a team’s config is missing or unrecognized, fall back to `teamCleanup` to at least remove team state files.
- The entire cleanup must be best-effort: if anything fails, log the failure but do **not** prevent the rest of the session-end flow from completing, and do **not** throw.
- Do not introduce any mandatory dependencies; if team state directories or manifests are missing, skip cleanly.

**Non-goals:**
This is not about fixing upstream signal-forwarding behavior; it is a pragmatic measure to reduce orphaned panes/windows when `SessionEnd` runs normally.


We're seeing several reliability gaps in team orchestration after interruptions or under edge conditions. The fixes should address the following acceptance criteria:

---

**1. Team resume must restore active worker monitoring**

When a team is interrupted (crash, disconnect, or stop) and later resumed via `resumeTeam`, the watchdog loop starts with an empty worker map. Any tasks that were `in_progress` at the time of interruption are not tracked, so they never get completion/failure checks or cleanup. After a resume, the watchdog must automatically recapture every task that is still marked `in_progress` on disk, with its owning worker and pane mapping, so monitoring can continue uninterrupted.

**2. Inbox cursor must gracefully skip corrupted JSONL lines**

If a worker's inbox JSONL file contains a line that fails to parse as JSON (partial write, corruption, etc.), the current inbox reader stops at that line and never advances the cursor. This permanently wedges the worker's message delivery: no further messages are ever read, and the cursor never moves past the bad line. The reader should instead log a warning, skip the unparseable line, advance the cursor past it, and continue reading subsequent valid messages. The cursor must reflect progress so that a subsequent read call does not re-read the same malformed line.

**3. Watchdog alive checks should run in parallel**

The watchdog loop calls `isWorkerAlive` sequentially for each active worker, which introduces unnecessary delay proportional to the number of workers. The alive checks should be issued in parallel (e.g., `Promise.all`) so that wall-clock latency does not scale with worker count.

**4. Watchdog should tolerate transient failures before giving up**

If a watchdog tick throws an error (e.g., a transient tmux communication failure), the watchdog currently logs it and continues. However, it has no failure count; repeated tick-level exceptions can go unnoticed, and there is no circuit-breaker to mark the team as failed after persistent errors. The watchdog should count consecutive tick failures and, after a small threshold (e.g., 3), write a failure marker file (e.g., `watchdog-failed.json`) and stop itself so the team can be diagnosed and not run silently broken. Successful ticks should reset the counter.

**5. `markTeamPhase` must be idempotent for all phases except the `team-fix` retry loop**

Calling `markTeamPhase` with the phase the pipeline already has should be a safe no-op that returns success without mutating state, avoiding unnecessary history entries or re-entrant side-effects. The sole exception is `team-fix` → `team-fix`: that transition increments the fix-loop retry count and must execute its normal logic even if the phase already is `team-fix`. This idempotency removes ordering sensitivity for callers that may re-trigger the same phase.

fix(team): reconstruct workers on resume, handle malformed inbox, parallel watchdog (#1078)


**Title:** Add git working tree status to HUD

**Background**
The HUD already shows the git repo name and current branch. Users coming from shell-based statuslines miss an at-a-glance summary of working‑tree dirtiness and remote divergence. We should add an optional element that displays a compact git‑status summary, matching the information `git status --porcelain -b` provides.

**Behavior**
- Display a single element with the following non‑zero counts:
  - Staged: `+N`
  - Modified (unstaged): `!N`
  - Untracked: `?N`
  - Ahead of remote: `⇡N`
  - Behind remote: `⇣N`
- Categories that are zero should be omitted from the output.
- When **all** counts are zero (clean working tree, no ahead/behind), the element should render **nothing at all** so the HUD stays uncluttered.
- If the `git status` command fails for any reason (not a repo, etc.), the element must silently return nothing — no crash, no error in the HUD.
- Use `git status --porcelain -b` as the data source. The first line contains branch and ahead/behind brackets; each subsequent line is a two‑character porcelain status where the first character is the index (staging) column and the second is the working‑tree column. `??` means untracked. An index column that is neither a space nor `?` means staged. An `M` or `D` in the working‑tree column means modified/unstaged. A file can be both staged and unstaged (e.g., `MM`); count both.

**Configuration & presets**
- Introduce a `gitStatus` boolean in the element configuration, defaulting to `false` for backward compatibility.
- Enable the element by default in the `focused`, `full`, and `dense` presets. Keep it disabled in `minimal` and `opencode`.
- In the default element ordering, place it on `line1` immediately after `gitBranch`.

**Performance**
- Cache the parsed status counts with the same 30‑second TTL that the existing git‑repo and git‑branch HUD elements use, so we don't spawn a git process on every single render tick.

**Integration**
- Follow the same pattern as the existing git HUD elements: expose the new renderer from the git‑elements module, import it in the main render pipeline, call it when the toggle is on, and insert the result into the rendered‑elements map. Remember to clear the new cache in any cache‑reset hook the existing elements use.

Addresses part of #2222.

feat(hud): add git working tree status element


# Bug Report: Workers exit prematurely after mailbox ACK/progress replies

**Context**: Team mode (runtime v2) – the multi-agent orchestration system.

**Observed behavior**
When a worker receives a message via its mailbox (e.g., a status query or sub-request), it sends an ACK or progress reply and then **stops executing** entirely, even though its assigned task is not yet complete and other team tasks remain pending. The team stalls.

**Expected behavior**
A mailbox ACK or progress report is **not** a stop signal. After replying, a worker should:

- Continue executing its currently assigned task if it isn't actually complete.
- If the current task is genuinely done, look for the next feasible team task and start working on it.

Workers should only exit after they have definitively completed or failed a task and performed the required task-status transition (and no further work is immediately available).

**Root cause / instructions mismatch**
The worker’s startup trigger messages, mailbox trigger messages, and the task-instruction overlay all contain language that effectively instructs the worker to **“exit immediately after transitioning task status.”** This is ambiguous when a transition happens as part of a mailbox reply: the worker treats the reply itself as the terminal action and shuts down. Additionally, the trigger wording lacks an explicit “keep executing your assigned or next feasible work” directive.

**Impact**
Once any mailbox communication occurs, the team silently loses worker capacity. Tasks go unworked, the team can't make progress, and the orchestration watchdog may eventually restart the dead worker or requeue tasks, causing wasted token spend and unpredictable delays.

**Desired fix scope**
Update the worker-facing instruction text in the three spots where workers receive lifecycle guidance:

1. **Startup inbox trigger** (the message that first prompts a worker to read its inbox and begin work)
2. **Mailbox trigger message** (the prompt sent when new mailbox messages arrive)
3. **Task instruction / worker overlay** (the steps list that workers see when a task is dispatched)

In all three, replace any “exit immediately after transitioning” language with a rule that says:
- ACK or progress replies are not a stop signal.
- Keep executing your assigned or next feasible work until a task is truly complete or failed, only then transition and exit.

This is purely a **prompt engineering / instruction update** — no logic changes to the orchestration runtime itself. The fix should ensure all future worker instances consistently stay alive across mailbox communication.

fix(team): keep workers running after mailbox replies


**Title:** Stale workflow skill slots keep blocking agent exit after autopilot/Ralph/ralplan sessions finish

**Background**
When a persistent-mode session (autopilot, Ralph, or ralplan) completes, fails, or is cancelled, the agent should eventually be allowed to stop without being blocked by that session’s workflow slot. Currently, the stop-gating logic can treat the slot as still live even though the mode-state file already records a terminal state (`active: false`, a `completed_at` timestamp, or a terminal phase like `"complete"`, `"cancelled"`, `"failed"`, `"done"`, `"stopped"`). This causes the agent to spin, refusing to exit because it thinks the session is still running.

**Desired behavior**
Before stop enforcement uses the workflow skill ledger to decide whether to block, the system must reconcile the ledger with the ground-truth mode-state files for the canonical workflow modes: `autopilot`, `ralph`, and `ralplan`. For each of those slots that does not already have a `completed_at` marker, the system should inspect the corresponding mode-state file. If that file indicates a terminal or inactive session (by any of the following: `active` is `false`, a non-empty `completed_at` string is present, or the phase / current_phase / status field is one of the recognized terminal values), the slot must be tombstoned (set `completed_at`) so that subsequent stop-gating does not block on it.

**Constraints**
- This reconciliation must be best-effort and never cause a hard failure; if the skill-state module or mode-state file is unavailable, enforcement falls back to the direct mode-state checks that already exist.
- The reconciliation should happen early in the persistent-mode check, before any stop-blocking decisions are made.
- The set of recognized terminal phase strings should include: `complete`, `completed`, `failed`, `cancelled`, `canceled`, `cancel`, `done`, `stopped`.
- The reconciliation must update both the session-scoped and root-scoped ledger copies when it makes a change, so future invocations see the tombstoned state.
- Only the three canonical workflow modes (autopilot, ralph, ralplan) should be reconciled; other skill slots are not affected.
- The logic should not interfere with context-limit / critical-context stops.

**Acceptance criteria**
- After an autopilot session’s mode-state file reaches a terminal state (e.g. `{ active: false, phase: "complete", completed_at: "..." }`), a call to check persistent modes for that session returns `shouldBlock: false`, and the skill ledger records a `completed_at` timestamp for the autopilot slot.
- The same holds for ralplan sessions with `current_phase: "complete"` and for ralph sessions with `active: false`.
- Sessions that are still active (no terminal indicators) are not incorrectly tombstoned.
- If the skill-state module cannot be loaded, the system still correctly decides based on the original mode-state checks.

fix: tombstone terminal workflow skill slots (#2965)


**Title:** Hook bridge debug warnings corrupt JSON stdout output

**Description**

When the hook bridge CLI processes an input with an unrecognized field, it emits a diagnostic warning to stdout:

```
[bridge-normalize] Unknown field "some_field" passed through for hook "pre-tool-use"
```

This string is printed *before* the single-line JSON payload that the bridge is expected to emit. Because the hook bridge protocol is documented to produce exactly one machine‑readable JSON object on stdout, any extra output breaks downstream consumers – including the Claude hook runner – which interpret the entire stdout as a single JSON document and fail to parse it.

The issue occurs because Node.js treats `console.debug` as an alias for `console.log`; both write to stdout. Any message that is not part of the JSON contract should never appear on stdout.

**Steps to reproduce**
1. Install the latest `oh-my-claudecode` plugin.
2. Run a hook invocation with an input object that contains a field not present in the known hook fields (e.g., `totally_custom: "hello"`).
3. Observe that stdout contains the debug warning on its own line, followed by the JSON output.

**Expected behavior**
- The stdout stream must contain nothing other than the JSON payload (a single line of valid JSON).
- Diagnostic/warning messages about unknown fields should be directed to stderr, leaving stdout clean for downstream JSON parsers.

**Impact**
- CLI integrations that rely on stdout being a well-formed JSON stream are broken whenever an unknown field passes through.
- This makes the bridge unreliable for automation and tooling that consumes its output.

**Additional context**
- The hook bridge documentation states that stdout should carry the JSON response.
- Any warning or debug output should go to stderr to avoid interfering with the protocol.

fix(hook-bridge): redirect unknown-field debug log from stdout to stderr


**Summary**: Codex CLI workers launched by `/omc-teams` never receive their initial task instruction. The tmux pane spawns but the input field stays empty because Codex CLI uses an Ink-based TUI that doesn’t accept text injected via `tmux send-keys`. The team mode already has a “prompt mode” workaround for Gemini (passing the task via the `-p` flag), but it was never extended to Codex.

**Acceptance criteria**:
1. When a Codex worker is spawned for a team task, the initial task instruction is passed as a **positional argument** to the `codex` CLI (i.e., `codex [OPTIONS] [PROMPT]`). There is no flag; the instruction follows the options directly.
2. The prompt-mode infrastructure must not require a flag to classify an agent as “prompt mode.” An agent contract with `supportsPromptMode: true` and no `promptModeFlag` should still be treated as a prompt-mode agent (skipping `send-keys` for the task and trust‑confirm steps).
3. The Gemini path (flag `-p`) must continue to work unchanged.
4. The compiled bridge (`bridge/runtime-cli.cjs`) must mirror the same behavior so that the plugin distribution stays consistent with the source.
5. The existing test for Gemini prompt-mode spawn must still pass after the generalization, and the coverage must include the Codex positional‑prompt path. (No need to add new test suites; adjust existing ones accordingly.)

**Implementation notes / constraints**:
- The model contract for Codex (`CONTRACTS.codex`) must declare `supportsPromptMode: true` without a `promptModeFlag`.
- `isPromptModeAgent()` should rely solely on `supportsPromptMode`, not on the presence of a flag.
- `getPromptModeArgs()` should return the instruction as a single-element array when no flag is defined, and `[flag, instruction]` when a flag is defined.
- The runtime spawn logic that builds the worker launch command already uses `getPromptModeArgs()`; no change to the spawn wiring should be needed beyond the contract/helper updates.

fix(team): add prompt-mode support for codex CLI workers in omc-teams (#1030)


**Title:** Add ZC1835 — warn when `smartctl -s off` disables drive SMART monitoring

**Background**
`smartctl` is the standard utility for querying and changing S.M.A.R.T. self‑monitoring on ATA/SCSI devices. A pattern that causes silent fleet health regression is `smartctl -s off <DEV>` (or its long‑option equivalent `--smart off`). After the drive stops recording attribute counters, `smartctl -H` will keep returning PASSED right up until a catastrophic failure, defeating any monitoring built on top of smartd. Operators often run this without realising the consequences.

**Request**
Create a new kata, **ZC1835**, that detects direct invocations of `smartctl` where the `-s` / `--smart` flag is immediately followed by the literal value `off`.

**Behaviour**
- Severity: **warning**.
- It must fire when the arguments contain **`-s off`** or **`--smart off`** (exact token matching).
- It must **not** fire for `-s on`, `--smart on`, any other value, or for `smartctl` invocations that don’t include the flag at all (e.g. `smartctl -a $DISK` is safe).
- The violation message should explain that disabling SMART silences health data and recommend keeping it on while configuring `smartd.conf` for proactive alerts.

**Guidance**
- Follow the existing kata pattern (registration with `RegisterKata` using `SimpleCommandNode`, command‑name extraction, argument‑walking).
- Make sure the check matches only the top‑level command name `smartctl`.
- Add corresponding tests that cover the good cases (`smartctl -s on`, `smartctl -a`) and the bad case (`smartctl -s off`).
- Bump the version string in accordance with the kata count.

feat: ZC1835 — warn on `smartctl -s off` disabling drive SMART monitoring (#856)


**Title**: New kata: ZC1734 — flag direct overwrites of `/etc/passwd`, `/etc/shadow`, `/etc/group`, `/etc/gshadow`

**Background**
The user-identity files `/etc/passwd`, `/etc/shadow`, `/etc/group`, and `/etc/gshadow` are protected by locking tools (`vipw`, `vigr`, `useradd`, `usermod`, `passwd`). Any command that writes to these files directly (via `cp`, `mv`, `tee`, `install`, `dd`, or shell redirections like `>` / `>>`) bypasses the lock and risks corruption, race conditions, or locking users out. We currently have no check for this pattern.

**Request**
Add a new kata with ID `ZC1734` at **severity error** that detects when any of the following commands receive one of the four paths as an argument, and similarly flags plain shell redirections that target those paths (e.g., `echo ... > /etc/passwd`, `cmd >> /etc/group`).

The kata should:

1. Match the commands `cp`, `mv`, `tee`, `install`, `dd` when the **target** path (the last argument for `cp`/`mv`/`install`/`dd`, or any argument for `tee`) is one of the four identity-file paths.
2. Match **redirections** where a `>` or `>>` token is immediately followed by one of those paths (as a separate argument, since Zsh word-splits redirections that way in the AST).
3. For any violation, report a message that mentions the offending command/path and recommends using `vipw -e` / `vigr -e` or the proper user-management tools (`useradd` / `usermod` / `passwd`).

**Non‑goals**
- Do not warn on read‑only commands (e.g., `cat /etc/passwd`) or on paths that are only indirectly supplied (e.g., through variable expansion).
- No need to consider symlinks or files outside the root directory; match literal paths only.

**References**
- Existing katas like ZC1733 illustrate the pattern for registering a `SimpleCommandNode` check and constructing a Violation.
- The kata should be placed in the usual `pkg/katas/` directory and follow the existing naming convention (`zc1734.go`).
- Please also bump the Katas count version comment in `pkg/version/version.go` when done (0.7.30 → 0.7.31).

feat: ZC1734 — flag overwrites of `/etc/passwd|shadow|group|gshadow` (lock bypass) (#754)


**Request**

Add a new “lockout” kata `ZC1831` that flags dangerous `systemctl` invocations on SSH units. We already have `ZC1815` for network units; this is the same idea for SSH.

**Behavior**
- Error when any of the following `systemctl` actions is used with an SSH-related unit name:
  - `stop`
  - `disable`
  - `mask`
- SSH-related unit names to detect:
  - `ssh`, `sshd`, `ssh.service`, `sshd.service`, `ssh.socket`, `sshd.socket`, `openssh-server`, `openssh`
- Do not flag other actions (e.g. `reload`, `restart`, `status`) or non-SSH units.

**Severity**
- `error` – because running these commands remotely can lock out the next login. Existing sessions may survive, but `disable`/`mask` also persist across reboots.

**Message guidance**
- Tell the user that the command blocks SSH: existing sessions survive but reconnects fail; `disable`/`mask` survive reboots; use `reload sshd` for config changes.

**Rationale**
- Identical pattern to `ZC1815` (network lockout check). Mirror its structure: a guard map of unit names, registration for `SimpleCommandNode`, and a check function that matches the command name, action, and arguments.

Please keep the version comment updated to reflect the new kata count.

feat: ZC1831 — error on systemctl stop/disable/mask ssh/sshd locking out remote login (#850)


```
Title: Missing check: `alias -g` / `alias -s` should not appear in scripts

### Problem
Zsh’s `alias -g` (global alias) expands *anywhere* on the command line, not just in command position. `alias -s` (suffix alias) causes a bare word like `file.log` to run a command when used as the command. Both are interactive conveniences that produce surprising, hard‑to‑audit substitutions when they slip into scripts.

Today `zshellcheck` silently accepts these patterns — no warning, no hint that the script does something non‑obvious.

### Desired behavior
Add a built‑in check that triggers a **warning** (severity `warning`) whenever `zshellcheck` encounters `alias -g` or `alias -s` in analyzed input. The warning message should explain why these alias forms are surprising in scripts and recommend using a function or keeping them in `~/.zshrc` where they are discoverable.

The check **must** fire for:
- `alias -g NAME=...`
- `alias -s ext=...`
- combined short‑option groupings that contain `g` or `s`, e.g., `alias -gs ...` (it’s acceptable to emit a single violation per `alias` invocation, even if both flags are present)

The check **must not** fire for:
- regular aliases: `alias ll='ls -l'`
- bare `alias` (listing aliases)
- `alias` with other flags that do **not** include `g` or `s`

### Impact
Scripts that use global or suffix aliases become extremely difficult to maintain and review — a word like `G` or `data.csv` stops meaning what it looks like. Catching these at analysis time prevents confusion and reduces the chance of subtle bugs. This is a natural gap for a tool that already understands Zsh‑specific syntax and aims to improve script quality.
```

feat: ZC1771 — warn on alias -g / alias -s in scripts (#790)


**Title:** New Kata: flag `systemctl` output subcommands missing `--no-pager`

**Severity:** style
**Suggested ID:** ZC1209

**Problem**
When `systemctl` is used in non-interactive scripts (e.g. `status`, `list-units`, `list-timers`, `show`) without `--no-pager`, it may attempt to pipe its output through a pager, which hangs in automated execution environments.

**Request**
Add a new Kata that detects calls to `systemctl` that use a pager-triggering subcommand and are missing the `--no-pager` flag. The check should:

- flag the following subcommands when `--no-pager` is absent: `status`, `list-units`, `list-timers`, `show`
- not flag `systemctl` calls that already contain `--no-pager`, regardless of argument order
- ignore other subcommands (e.g. `start`, `stop`, `restart`) where the pager is irrelevant
- suggest adding `--no-pager` or piping to `cat` in the violation message
- be registered with `SeverityStyle`

Register the kata under ID **ZC1209** following the existing pattern for simple-command checks.

feat: Add ZC1209 — use systemctl --no-pager in scripts (#234)


**Feature Request: Warn on `gcore PID` and `strace -p PID` (ptrace attach)**

Add a new Kata (lint rule) in ZShellCheck that flags uses of `gcore` or `strace` when they attach to a running process via ptrace, because such commands expose the target process’s memory, environment variables, and system-call arguments in production scripts.

**External behaviour**

- **Severity:** Warning.
- **`gcore`:** Any invocation of the `gcore` command with at least one argument that is *not* a help/version flag (`-h`, `--help`, `-?`, `--version`) should trigger the violation. That is, `gcore 1234` triggers, but `gcore --help` and `gcore --version` do not.
- **`strace`:** Only invocations where the `-p` option is present (e.g., `strace -p 1234` or `strace -f -p 1234`) should trigger. Invocations like `strace ls` that trace a child process without `-p` should not.
- **Message:** The warning message should clearly state that the command attaches via ptrace and exposes memory, environment, and system-call arguments, and should advise against using ptrace in production scripts (suggesting alternatives like `coredumpctl` for captured cores).

**Context**

`gcore PID` writes a core dump of a running process; `strace -p PID` streams every system call of a target process. Both rely on ptrace and can leak secrets such as environment variables and in-memory credentials. Production scripts that invoke these tools on arbitrary processes—especially as root—pose a security risk.

**Acceptance criteria**

1. `gcore 1234` produces a warning.
2. `gcore --help` and `gcore --version` produce no warning.
3. `strace ls` (no `-p`) produces no warning.
4. `strace -p 1234` produces a warning.
5. `strace -f -p 1234` (with or without other flags) still produces a warning because `-p` is present.

feat: ZC1828 — warn on gcore / strace -p dumping target process memory (#847)


Add a new check (ZC1680) that flags uses of `ansible-playbook --vault-password-file` (and the `ansible` command) when the provided path is under a world-traversable directory: `/tmp/`, `/var/tmp/`, or `/dev/shm/`.

**Why**
A vault password file stored in a world-traversable location can be race-read by any local user, exposing all secrets protected by the vault. The check should guide users to place vault keys in root-owned, mode‑0400 files under `/etc/ansible/` or to use a secure vault‑password helper instead.

**Behavior**
- Trigger on both `ansible-playbook` and `ansible` commands.
- Accept the two standard forms:
  - `--vault-password-file=/path` (joined)
  - `--vault-password-file /path` (separate argument)
- Only flag paths that begin with `/tmp/`, `/var/tmp/`, or `/dev/shm/`.
  - Paths like `/etc/ansible/vault.pass` or `$HOME/.ansible/vault.pass` should **not** be flagged.
- If the flag is absent altogether, no violation should be reported.
- Severity: **error** (security-sensitive).
- The violation message should clearly state the issue and recommend safe alternatives.

**Implementation notes**
- Register the check for `SimpleCommand` nodes, as other command‑focused katas do.
- The check must handle both the joined and separated argument syntax without false positives or false negatives.
- Any safe‑path prefix list (the three unsafe directories) should be applied case-sensitively as given.

feat: ZC1680 — flag ansible-playbook vault-password-file in world-traversable path (#700)


**Feature Request: Add ZC1685 – detect `sleep infinity` keep‑alive pattern**

**Problem**
In container images and systemd units, `sleep infinity` is frequently used as a last‑line keep‑alive so the container/unit doesn’t exit immediately. However, GNU `sleep` does not install a SIGTERM handler. When an orchestrator (e.g. `docker stop`, `systemctl stop`) sends SIGTERM, the process ignores it, forcing the orchestrator to wait for the full grace period before sending SIGKILL. This results in:

- Unhealthy stops being reported by the orchestrator.
- Loss of any cleanup that shell‑level signal handlers would have performed.
- Misleading logs because the process appears to hang.

The pattern is pervasive and rarely intentional – most users assume `sleep infinity` reacts to signals.

**Expected behavior**
ZShellCheck should emit an **info**‑level finding whenever it encounters **`sleep infinity`** used as a standalone command. The finding should:

- Warn that `sleep infinity` does not trap SIGTERM, leading to delayed kills and missed cleanups.
- Suggest safe alternatives such as `exec tail -f /dev/null` or frontending with `tini`/`dumb-init`.
- Only trigger when the first argument is the **literal** string `infinity`. Do **not** flag:

  - `sleep 30`
  - `sleep $timeout`
  - `sleep "infinity"` (if the parser resolves it differently – the literal case should be caught)

- Be assigned the ID **ZC1685** with severity `info` (SeverityInfo).

**Edge cases / invariants**

1. **Literal match only** – The check must not fire when the argument comes from a variable expansion, command substitution, or a numeric value. Only an explicit `infinity` token should trigger it.
2. **Command‑only** – The check should inspect simple commands where the command name is `sleep`. Shell built‑ins, functions, or aliases that resolve to `sleep` may or may not be caught (lower priority), but the literal `sleep infinity` invocation is the primary target.
3. **Message content** – The violation message should succinctly explain the SIGTERM issue and mention at least one concrete safe replacement. The exact wording may vary, but must remain helpful and actionable.
4. **No false positives on valid sleeps** – `sleep 0`, `sleep 5`, `sleep 1h`, etc. must not trigger.

**Impact**
Adding this check will help users avoid a common container‑lifecycle pitfall and encourage patterns that respect SIGTERM, improving automated deployments, CI/CD pipelines, and systemd‑managed services.

feat: ZC1685 — flag sleep infinity SIGTERM-insensitive keep-alive (#705)


```
Add a new lint rule (kata) that flags unsafe disabling of Zsh's NOMATCH option.

Behavior:
- Flag `unsetopt NOMATCH` and `setopt NO_NOMATCH` (the equivalent) as a warning.
- Do NOT flag `setopt NOMATCH` (which restores the safe default).
- The check must be case-insensitive and ignore underscores in option names,
  so `unsetopt no_match` or `UnsetOpt NO_MATCH` are also caught.
- Unrelated `unsetopt` options (e.g., `unsetopt HIST_IGNORE_DUPS`) must not trigger.

Severity: warning.
Rationale:
Zsh's default NOMATCH causes unmatched globs to error out, preventing silent bugs
where a nonexistent glob becomes a literal pattern. Disabling it reverts to POSIX
behavior where the pattern is passed verbatim to the command, which can result in
unintended operations when the command interprets the literal string.

Message suggestion: warn that `unsetopt NOMATCH` silences the unmatched-glob error,
and recommend using the `*(N)` glob qualifier per-pattern or scoping with
`setopt LOCAL_OPTIONS; setopt NULL_GLOB` inside a function.

Implementation notes:
- Follow the existing kata registration pattern (see `RegisterKata` and the
  `ast.SimpleCommandNode` check in other katas).
- After adding the kata, bump the project version constant in the version package
  (the version is derived from the total number of katas, so adding one should
  increment the minor version).
```

feat: ZC1830 — warn on unsetopt NOMATCH silencing unmatched-glob safety (#849)


**Title:** Add style rule: flag `cat /proc/cpuinfo` and recommend `nproc`

**Description:**
We need a new Kata (next available ID, suggest ZC1266) that detects when a script uses `cat /proc/cpuinfo` to obtain the number of CPU cores. The rule should emit a **style-level** violation suggesting `nproc` instead, because `nproc` is portable and purpose-built for this task (available on Linux and macOS via coreutils).

**Trigger condition:**
- The command is a simple command whose name is exactly `cat` (no path prefix, no `sudo`/`command` prefix).
- One of its arguments is the **literal string** `"/proc/cpuinfo"` (not a variable expansion, not a partial match, not a glob).

**Expected message:**
> Use `nproc` instead of parsing `/proc/cpuinfo` for CPU count. `nproc` is portable and available on Linux and macOS (via coreutils).

**Non‑triggers (safety):**
- `nproc` (or any other command) must not be flagged.
- `cat` with arguments that do not include the exact literal `/proc/cpuinfo` (e.g., `cat /proc/cpuinfo.bak`, `cat "$file"`) should not trigger.
- Constructs like `sudo cat /proc/cpuinfo` or `/usr/bin/cat /proc/cpuinfo` are acceptable to ignore for this initial implementation.

**Additional notes:**
- The check should integrate into the existing Kata registration mechanism for simple commands.
- Bump the project version to reflect the new Kata count once it is registered.

feat: Add ZC1266 — use nproc instead of parsing /proc/cpuinfo (#292)


```txt
Request for ZC1643: Prefer `$(<file)` over `$(cat file)` in command substitutions

ZShellCheck currently lacks a style check for a classic Zsh performance anti-pattern:
using `$(cat somefile)` inside a command substitution instead of the builtin `$(<somefile)`.
In Zsh, `$(<file)` reads the file directly without forking or exec'ing an external process,
while `$(cat file)` spawns a child `cat` process, pipes the content, and waits. For hot
paths or scripts that do this frequently, the overhead is unnecessary.

Desired behavior:
- Detect any occurrence of `$(cat ...)` appearing inside a command substitution (e.g.,
  `echo "$(cat /etc/hostname)"`, `print -r -- "$(cat file)"`, etc.) and emit a style‑level
  violation with a clear suggestion to use `$(<FILE)`.
- Do **not** flag plain `cat somefile` commands that are not inside `$()`. A bare
  `cat /etc/os-release` (for logging or direct output) should be ignored.
- The check should be implemented as a new kata with ID **ZC1643** and severity **style**.

Edge cases / scope:
- Only the `$(cat …)` pattern inside a command‑substitution context should be flagged.
- The kata should inspect `SimpleCommand` nodes (where command substitution arguments
  would appear) and scan the argument text for the substring `$(cat `.
- The violation’s field values (KataID, SeverityStyle, Line/Column from the command’s
  start token) should follow the conventions used by other style katas.

Please add the new kata implementation and accompanying test, following the existing
patterns in the repository (kata registration, test structure using `testutil.Check` and
`testutil.AssertViolations`). Adding this rule will help users write more efficient Zsh
scripts by eliminating useless‑use‑of‑cat inside command substitutions.
```

feat: ZC1643 — prefer `$(<file)` over `$(cat file)` (no fork) (#662)


Implement a new kata ZC1711 to catch catastrophic misuse of `etcdctl del` with an empty prefix.

**Why**
`etcdctl del --prefix ""` (or `--from-key ""`) deletes every key in the entire etcd cluster, including Kubernetes apiserver state when etcd is the datastore. This is an easy copy‑paste mistake that operators must never make.

**What to flag**
Flag **only** when the command is `etcdctl del` and either `--prefix` or `--from-key` is immediately followed by an empty string argument (`""` or `''`).

**What NOT to flag**
- `etcdctl get --prefix ""` (read‑only operations are fine)
- `etcdctl del --prefix /app/` (any non‑empty prefix is fine)
- `etcdctl del` without these flags
- `etcdctl` subcommands other than `del`

**Severity**
Error. Include a clear message that explains the danger and advises scoping to a specific prefix and/or reviewing with `etcdctl get --prefix --keys-only` before deletion.

**Registration**
Follow the existing kata conventions for `SimpleCommand` checks; register it like a normal kata.

feat: ZC1711 — flag etcdctl del --prefix "" wipes whole keyspace (#731)


**Title:** Add Kata ZC1183 – warn when `ls -t` (and variants) is used; suggest Zsh glob qualifiers

**Type:** feature request
**Priority:** normal
**Labels:** kata, style, zsh-idiom

### Motivation

Zsh glob qualifiers `*(om[1])` and `*(Om[1])` can order files by modification time without spawning an external `ls` process. Replacing `ls -t` patterns with glob qualifiers improves performance and follows Zsh best practices.

We should detect the common patterns `ls -t`, `ls -tr`, `ls -lt`, and `ls -ltr` and suggest the corresponding glob qualifier alternatives.

### Desired behavior

- A **new style-level Kata**, `ZC1183`, fires when a simple command with the executable name `ls` is called with one of the flags `-t`, `-tr`, `-lt`, or `-ltr`.
- The violation message should mention both `*(om[1])` (newest file) and `*(Om[1])` (oldest file) and explain that glob qualifiers avoid spawning an external process.
- The check must **not** fire for `ls` invocations that do **not** include a time-sort flag (e.g., `ls -la`, `ls`, `ls -lR` are all quiet).

### Acceptance criteria

- The new kata is registered under the ID `ZC1183` with severity `style`.
- It inspects `SimpleCommand` nodes; when `cmd.Name` is an identifier equal to `"ls"` and any argument matches one of the four flag strings exactly (`-t`, `-tr`, `-lt`, `-ltr`), a Violation is emitted.
- The violation message includes the recommended glob qualifier forms.
- The version constant in `pkg/version/version.go` is bumped by one minor increment (reflecting one new kata).

feat: Add ZC1183 — use Zsh glob qualifiers instead of ls -t (#204)


**Feature request: warn when `ls -S` is used and suggest native Zsh glob qualifier**

`ls -S` (sort by size, largest first) forces an external process even though Zsh’s built-in glob qualifier `*(OL)` can order files by size without spawning `ls`. The same applies to `-Sr` (reverse/smallest first, equivalent to `*(oL)`) and to flag combinations like `-lS`/`-lSr`.

We’d like ZShellCheck to detect any `ls` command whose arguments include one of the size‑sorting flag forms (`-S`, `-Sr`, `-lS`, `-lSr`) and emit a **style**‑level suggestion recommending the native Zsh glob alternative.

Expected behaviour:
- A violation is emitted only for the command `ls` (not for another program that happens to accept the same flags).
- The violation message should mention both `*(OL)` (largest‑first) and `*(oL)` (smallest‑first) so the user knows the available built‑in equivalents.
- Ordinary `ls -l` without a size‑sort flag should **not** produce a violation.

The check should be registered as kata **ZC1360** at severity **style**.

feat: ZC1360 — use Zsh `*(OL)` glob qualifier instead of `ls -S` (#373)


**Title:** Lexer swallows closing quote when double-quoted string contains escaped `\${` inside `${…}` expansion

**Observed behavior**
When a double-quoted string contains an escaped `\${` within a `${…}` expansion (e.g. `"${(%):-a\${b}"` or the more complex real-world line from powerlevel10k’s `p10k.zsh`), the lexer fails to recognize the closing `"`. The rest of the file is then treated as part of the string, which causes keywords that happen to appear later (like `for` in `for example`) to be mis-lexed as shell keywords, leading to cascaded tokenization errors.

**Expected behavior**
Zsh treats a bare `{` inside a `${…}` expansion as a literal character; only actual `${` opens a nested expansion. Therefore, after encountering an escaped `\${`, the remaining `{` is literal, and the outer expansion closes at the next unescaped `}`. The double-quoted string should terminate at its closing `"`, and everything after it should be lexed normally.

The same issue occurs without any escaping when a bare `{` appears inside `${…}` (e.g. `"${x:-a{b}"`); in zsh this is valid and the expansion ends at the first unescaped `}`, so the closing quote must still be recognised.

**Safety constraint**
Any fix must preserve correct handling of genuinely nested expansions such as `${x:-${y:-z}}`. The inner `${` must still be counted as a nested expansion, so that its own `}` decrements the depth and the outer expansion closes correctly at the final `}`.

**Severity**
This prevents scanning of real-world Zsh configurations that use this pattern and causes false-positive parse errors or crashes downstream.

fix: stop lexer swallowing strings on escaped ${


Add a new static‑analysis check (kata ID: ZC1853) that warns whenever a Zsh script or configuration enables the `MARK_DIRS` option.

### Problem

When `MARK_DIRS` is active, every filename produced by a glob that matches a directory gets a trailing `/` appended. This breaks common constructs:

- `[[ -f "$f" ]]` rejects the result because a path with a trailing `/` is not a regular file.
- `rm -f *` skips directories entirely (GNU `rm` will not remove a directory without `-r`).
- Hash maps keyed on basenames create duplicate entries.

The option is off by default, but enabling it—especially at the top of a script—silently introduces these bugs. Users who only need directory‑only globs should use the `(/)` glob qualifier (e.g. `dirs=( *(/) )`) instead.

### Acceptance criteria

1. **Trigger**
   The check shall fire when the tool encounters either:
   - `setopt MARK_DIRS` (any capitalization/underscore variation: `mark_dirs`, `Mark_Dirs`, etc.), or
   - `unsetopt NO_MARK_DIRS` (any normalization that maps to `NOMARKDIRS`).

2. **Non‑triggers**
   The check shall **not** fire for:
   - `unsetopt MARK_DIRS` (explicitly disabling the option – this is the safe default).
   - Unrelated `setopt` / `unsetopt` calls such as `setopt NOMATCH`.
   - Any other command that is not a `setopt` / `unsetopt` invocation.

3. **Severity**
   The violation must be reported at **warning** level.

4. **Message**
   The reported message must make clear that `setopt MARK_DIRS` (or `unsetopt NO_MARK_DIRS`) causes a trailing `/` to be appended to directory globs, explain the concrete breakage (`[[ -f … ]]` skips, `rm -f *` skips, map key duplication), and recommend keeping the option off while using `*(/)` when only directories are needed.

5. **Registration**
   The kata must be registered for the appropriate ZShellCheck rule pipeline so that it runs on every relevant input file (scripts, functions, configuration). Follow the established kata conventions used by sibling checks (e.g. other option‑warning katas).

feat: ZC1853 — warn on `setopt MARK_DIRS` appending trailing `/` to dir globs (#874)


```
Title: Add ZC1413 — prefer Zsh `whence -p` over `hash -t`

Severity: style (SC2034‑style “suggestion”)

Check
Detect any use of `hash -t cmd` in scripts, functions, and configuration and emit a
violation recommending the Zsh‑native `whence -p cmd` instead.

Rationale
`hash -t` is a Bash/ksh‑ism that prints the hashed path or fails when the command
hasn’t been hashed. `whence -p` always resolves the absolute path via PATH,
regardless of hash state, making it a more reliable idiom in Zsh.

Edge cases & safety
- Only flag `hash` invocations that contain the `-t` option.
- Do **not** flag `hash -r` (or `hash -d`, etc.) – those are unrelated.
- The violation should point to the line and column of the `hash` token, similar to
  how other katas report on the command name.
- Message text should explain the improvement clearly: “… `whence -p` always
  returns the absolute path, regardless of hash state.”

Implementation notes
- Register the new kata on the `SimpleCommandNode` (the node type used by existing
  checks that inspect command invocations). Follow the same `init` + `RegisterKata`
  pattern used by other style‑level katas, e.g. ZC1375 or similar.
- The new ID is **ZC1413**.
- After adding the kata, bump the project version so that the kata count is
  correctly reflected (version is derived from total katas).
```

feat: ZC1413 — use Zsh `whence -p` instead of `hash -t` (#428)


**Feature Request: Detect dangerous `pacman -Sy <pkg>` partial-upgrade pattern**

Add a new Kata (lint check) to ZShellCheck that warns when a command invocation of `pacman` uses the `-Sy` (or any combined flag containing `S` and `y` but **not** `u`) and also specifies at least one package name (a non‑flag argument). This pattern, known as a partial upgrade, refreshes the local database and then installs only the named package against the newer metadata without performing a full system upgrade (`-Syu`). The resulting mismatch between installed package versions and library dependencies frequently causes “error while loading shared libraries” failures and can leave the system in an inconsistent state.

**Acceptance Criteria**

- **Severity:** `warning`
- The check triggers on any simple command that:
  - starts with `pacman`
  - contains a flag argument that includes both `S` and `y` (e.g., `-Sy`, `-Sya`, `-Syl`) but does **not** include `u`
  - has at least one non‑flag argument (the package being installed)
  - *Examples that MUST trigger:* `pacman -Sy some-package`, `pacman -Sya some-package`, `pacman -Sy --noconfirm some-package` (where `--noconfirm` is a separate flag that does not affect `S`/`y`/`u`)
- The check must **not** trigger in the following safe cases:
  - Full upgrade with `-Syu`: `pacman -Syu package`
  - Install without database refresh: `pacman -S package`
  - Database refresh without installing: `pacman -Sy`
  - Database refresh with `-u` present in the same flag: `pacman -Syu package`
  - Commands that are not `pacman`
- The warning message should explain why this pattern is dangerous (partial upgrade, dependency closure breaks) and recommend using a full `pacman -Syu` before installing, or the combined `pacman -Syu --noconfirm <pkg>` for atomic upgrades in CI.

**Out of scope**
- Detecting partial upgrades expressed with separate `-S` and `-y` flags (e.g., `pacman -S -y package`). This first version should only handle the combined flag form.

feat: ZC1724 — flag `pacman -Sy <pkg>` (partial-upgrade footgun) (#744)


Add a new kata ZC1626 to detect when a `helm` command passes secrets on the command line via `--set` or `--set-string`.

**Trigger**
`helm install`, `helm upgrade`, or `helm template` where an argument after `--set`/`--set-string` is a `KEY=VALUE` pair and KEY (case‑insensitively) contains any of: `password`, `passwd`, `secret`, `token`, `apikey`, `api_key`, `api-key`, `accesskey`, `access_key`, `access-key`, `privatekey`, `private_key`, `private-key`.

**Severity**
`error` — the value ends up in the process list (visible via `ps`, `/proc/<pid>/cmdline`, shell history, audit logs).

**Message**
The violation message should include the offending subcommand, flag, and key, and recommend using `-f values.yaml` or `--set-file KEY=PATH` instead.

**Non‑violations**
- `--set`/`--set-string` with a non‑secret key (e.g., `replicas=3`)
- `-f` or `--set-file` even when they reference a path that includes a secret word (those do not expose the value in argv).

The check must be registered with the existing kata infrastructure.

feat: ZC1626 — flag `helm --set` secret key exposing value in process list (#645)


**Feature request: Add a check for `setopt SH_GLOB` (and `unsetopt NO_SH_GLOB`) that breaks Zsh-extended glob patterns**

**Problem**
Zsh’s extended glob features – null glob qualifiers `*(N)`, numeric ranges `<1-10>`, in-glob alternation `(alt1|alt2)`, and the `(#i)` / `(#c,m)` flag family – depend on `SH_GLOB` being **off** (the default). Turning `SH_GLOB` on forces strict POSIX-sh parsing, causing `(...)` to be reinterpreted as command grouping and many extended patterns to produce parse errors. Executing either

```zsh
setopt SH_GLOB
```
or

```zsh
unsetopt NO_SH_GLOB
```

silently breaks all downstream uses of Zsh-specific glob syntax, including common idioms recommended by other ZShellCheck rules.

**Expected behavior**
ZShellCheck should emit a **warning** whenever a command that enables `SH_GLOB` is encountered. Specifically:

- `setopt SH_GLOB` should trigger a warning.
- `unsetopt NO_SH_GLOB` should also trigger a warning, since it has the same effect.
- `unsetopt SH_GLOB` and `setopt` with unrelated options (like `NOMATCH`) should not trigger.

The warning should clearly explain that enabling `SH_GLOB` disables Zsh-extended glob patterns and recommend keeping the option off, scoping any truly necessary POSIX-sh glob usage with `LOCAL_OPTIONS`.

**Impact**
Users who source configuration or tooling that enables `SH_GLOB` (often unknowingly) break existing scripts that rely on Zsh’s glob features. A dedicated static check would catch this early and prevent difficult-to-debug shell errors.

feat: ZC1897 — warn on `setopt SH_GLOB` breaking Zsh-extended glob patterns (#918)


Add a new kata to detect `git commit --amend`.
- Severity: **warning** – rewriting history on shared branches causes problems when the commit has already been pushed.
- Message: “Avoid `git commit --amend` on shared branches — it rewrites history. Use `git commit --fixup` or create a new commit instead.”
- The check must fire for any `git commit` command that includes the `--amend` argument, regardless of other options or position. A plain `git commit` without `--amend` must not produce a violation.
- Use the next available kata ID (ZC1254).

feat: Add ZC1254 — avoid git commit --amend on shared branches (#279)


**Feature request: ZShellCheck should detect and error on wholesale container wipe commands**

We need a new Kata that flags **`podman system reset`** and **`nerdctl system prune` with `--all` (or `-a`) *and* `--volumes`** as error-level violations. These commands completely destroy every container image, volume, network, and pod on the host, making them dangerously easy to execute unintentionally in a script. Developers should be guided toward narrower, scoped removals.

**Acceptance criteria**

1. **podman system reset**
   Any occurrence of `podman system reset` (regardless of additional flags such as `--force`) triggers an error violation. The violation message should describe the full-host wipe and point the user toward safer alternatives like `podman rmi`, `podman volume rm`, or scoped `podman prune`.

2. **nerdctl system prune -a/--all + --volumes**
   When `nerdctl system prune` appears together with *both* a “wipe everything” flag (`-a`, `--all`, `-af`, `-fa`) *and* `--volumes`, trigger an error violation. The flag order does not matter; the combination is what makes the command destructive. The message should similarly explain the impact and suggest narrower commands (e.g., `nerdctl rmi`, `nerdctl volume rm`).

3. **False-positive avoidance**
   - `podman system df` (read‑only) must not fire the rule.
   - `nerdctl system prune` without the full destructive combination (i.e., missing `--all`/`-a` or missing `--volumes`) must not fire.
   - Other `podman`/`nerdctl` commands that remove individual resources (`rmi`, `image rm`, `volume rm`, scoped `prune`, etc.) must not fire.

4. **Violation details**
   The violation should carry `error` severity, point to the exact line and column where the command begins, and include a concise, actionable message.

The check should be registered as a standard ZShellCheck Kata so users can enable/disable it via the project’s configuration (`severity` filtering, `.zshellcheckrc` overrides, etc.).

feat: ZC1783 — error on podman system reset / nerdctl prune -a --volumes (#802)


We need a new kata, `ZC1770`, that warns on insecure GPG trust model usage.

• The kata must fire whenever a simple command calls `gpg` or `gpg2` with `--always-trust` anywhere among its arguments, or with `--trust-model always` (including the `--trust-model=always` form).
• It must *not* fire when `--trust-model` is set to any other value (e.g. `pgp`), nor for a plain `gpg` invocation without these flags.
• Severity: `warning`.
• The diagnostic message must clearly explain the risk (bypasses the web of trust, attacker-supplied key verifies cleanly) and offer actionable alternatives: drop the flag and either pin the expected fingerprint (comparing the signer after `--verify --status-fd 1`), or assign trust once via `gpg --edit-key KEYID trust`.

The tests should verify the above acceptance criteria and can be placed in the existing katatests directory following the project’s test conventions.

feat: ZC1770 — warn on gpg --always-trust / --trust-model always (#789)


Please add a new kata (ZC1434) that warns when a script uses `swapoff -a` or `swapoff --all`.

**Behavior**
- The warning must fire on *any* occurrence of `swapoff` with the flag `-a` or `--all`, regardless of other arguments.
- Do *not* flag `swapoff` with a regular file/device argument (e.g., `swapoff swap.img`).
- Severity: `warning`.

**Rationale & message**
Disabling all swap on a memory-constrained host pushes data back into RAM and can trigger the OOM killer. The message should convey this risk and recommend disabling specific swap files/devices after checking available memory (e.g., using `free -m`).

**Notes**
- Follow the existing kata registration pattern for simple commands.
- The check should inspect the AST for a `SimpleCommand` whose name is `swapoff`, then examine its arguments for the exact strings `-a` or `--all`.

feat: ZC1434 — warn on `swapoff -a` (OOM risk on memory-constrained hosts) (#451)


**Summary:** Add a new `style` kata `ZC1406` that flags use of `xargs -P` (and `xargs --max-procs`) and suggests the Zsh-native `zargs -P N` instead.

**Background**
Zsh ships a loadable `zargs` function (`autoload -Uz zargs`) that provides parallel execution (`-P`) without the quoting quirks of BSD/GNU `xargs`, and it keeps shell variables and functions in scope — something `xargs` cannot do because it spawns a new process per item. For Zsh-specific scripts, plugins, and configs, `zargs -P N` is almost always the better choice.

**Desired behavior**
When scanning a Zsh script, configuration, or function, if the command name is `xargs` **and** any of its arguments matches `-P`, `--max-procs`, or a “stuck” spelling like `-P4`, emit a violation.

**Rule definition**
- **Kata ID:** `ZC1406`
- **Severity:** `style` (cosmetic / idiomatic improvement, not a correctness bug)
- **Title:** Prefer Zsh `zargs -P N` autoload over `xargs -P N` for parallel execution
- **Message:** `Consider \`zargs -P N\` (autoload -Uz zargs) instead of \`xargs -P N\`. Parallel execution with Zsh functions in scope — no subshell-per-item.`
- **Check trigger:** any `SimpleCommand` whose `Name` is `xargs` and whose arguments list contains an entry that is exactly `-P`, exactly `--max-procs`, or starts with `-P` (i.e., `-P<digits>`).
  *Note:* `-P` on its own is sufficient to trigger; we don’t need to check whether a number follows.

**Constraints & edge cases**
- Do **not** flag `xargs` invocations that lack `-P`/`--max-procs` completely (e.g., `xargs -n 1 echo` is fine).
- The flag may appear in any position in the argument list.
- `--max-procs` is the long GNU option and should be treated identically.
- The attached form `-P4` (no space) is common and must be caught by a prefix check on the argument string.

**Implementation notes**
- Register the kata with the node type `SimpleCommand` using the existing `RegisterKata` helper, as is standard for all other command-level katas.
- Follow the existing `checkZC*` pattern: type-assert the node, check the command name, iterate arguments, return a single violation on first match, or `nil` if none.
- Add unit tests in the `pkg/katas/katatests` package following the project’s table-driven style. Provide at least:
  - a “valid” case: `xargs` without `-P` (no violation),
  - a “invalid” case: `xargs -P 4 cmd` (violation),
  - a “invalid” case with attached flag: `xargs -P4 cmd` (violation).
- Increment the version in `pkg/version/version.go` to reflect the new kata count (add 1 to the current kata total and adjust the version number accordingly; the comment documents the count→version mapping).

feat: ZC1406 — prefer Zsh zargs -P over xargs -P for parallel execution (#420)


**Title:** New check: error on `aws configure set aws_secret_access_key` / `aws_session_token` on the command line

**Description**
In Zsh scripts and configuration files, a common (but dangerous) pattern is to use `aws configure set aws_secret_access_key <VALUE>` or `aws configure set aws_session_token <VALUE>` to inject AWS secrets. This leaks the raw secret into the process list (`ps`) and into shell history, making it trivially obtainable by other users on shared hosts or CI runners. The command also writes the secret in plaintext into `~/.aws/credentials`, exacerbating the exposure window.

ZShellCheck should detect such invocations and report an **error**-severity violation. The check must:

- **Trigger** when a simple command is `aws configure set <KEY> <VALUE>` and `<KEY>` (case-insensitive) is exactly:
  - `aws_secret_access_key`
  - `aws_session_token`
  - or contains the substring `"secret"` (to catch custom or obfuscated secret keys)
- **Not trigger** for safe parameters like `region`, `output`, etc.
- **Error message** should explain that supplying the secret on the command line exposes it in `ps` / shell history, and suggest using IAM‑role‑based authentication (EC2 instance profile, IRSA on EKS, OIDC for GitHub/GitLab) or importing the value from stdin / a file with mode `0600` instead.

**Impact**
Without this check, secrets from `aws configure set …` silently end up in process listings and shell history, undermining the security posture of any Zsh scripted infrastructure.

**Additional context**
- The check should be implemented as a new kata (ZC number will be assigned by the maintainers).
- Please increment the project version according to the kata‑count convention after adding the check.

feat: ZC1575 — error on aws configure set aws_secret_access_key / aws_session_token (#593)


Implement a new style rule (kata) in ZShellCheck: ZC1673.

**What it should catch**: any simple command that is exactly `stty -echo` (the command name `stty` with a single argument `-echo`). The typical fragile pattern is wrapping password reads in `stty -echo; read ...; stty echo`, where a crash between the two `stty` calls leaves the terminal with echo disabled. The recommended replacement is `read -s` in Zsh (also works in Bash 4+).

**What it should not catch**:
- Other `stty` invocations like `stty echo`, `stty raw`, etc. – those are fine.
- `stty -echo` with additional arguments (e.g., `stty -echo raw`) – flag only the exact single-argument `-echo` usage.

The severity of the rule should be `style`.

**Definition**:
- Rule ID: `ZC1673`
- Message: something like "`stty -echo` to mask password entry is fragile — a crash leaves the terminal echo-off. Use `read -s VAR` (Zsh / Bash 4+) instead."

**Version bump**: the version string is derived from the kata count. After adding this rule, the total number of katas becomes 670, so update the version constant accordingly.

feat: ZC1673 — prefer Zsh read -s over stty -echo bracketing (#693)


**Title:** New kata: flag `shopt` usage in Zsh (Bash-only builtin)

**Description:**
`shopt` is a Bash builtin that does not exist in Zsh. Users writing Zsh scripts, plugins, or configuration files sometimes mistakenly use `shopt` to set shell options. We need a static analysis rule that catches any use of `shopt` as a command name and suggests using `setopt`/`unsetopt` instead.

**Acceptance Criteria / Behavioral Requirements:**
- Any *simple command* whose command name is exactly `shopt` should produce a warning-level violation.
- The violation message should clearly state that `shopt` is a Bash builtin and direct the user to `setopt`/`unsetopt`.
- The kata must be a `SimpleCommand` check, registered with the kata registry with severity `warning`.
- The kata ID should be the next available ZC number (likely ZC1296). The version constant in `pkg/version/version.go` must be incremented to reflect the new kata count (1 kata added → bump to 0.2.93 if currently at 0.2.92 with 292 katas).
- A corresponding test file must be added in `pkg/katas/katatests/`. It should use the standard test utilities (`testutil.Check`, `testutil.AssertViolations`) and cover at least:
  - A case where `shopt -s extglob` triggers a violation with correct line/column and message.
  - A case where `setopt extendedglob` does *not* trigger the violation (ensuring the check is specific to `shopt`).

**Implementation Notes:**
- Follow the existing kata pattern: register the kata in an `init()` function with `RegisterKata(ast.SimpleCommandNode, Kata{...})`, and implement a `checkZC1296` function that inspects the `*ast.SimpleCommand` node.
- The violation should capture the token’s line and column from `cmd.Token`.
- No additional configuration or options are needed yet.

feat: Add ZC1296 — avoid shopt in Zsh, use setopt/unsetopt instead


Add a new kata `ZC1630` (severity: `warning`) that flags `php -S` invocations when the bind address listens on all network interfaces. Specifically, the kata should trigger when the argument immediately after the `-S` flag starts with one of these prefixes:

- `0.0.0.0:`
- `*:`
- `[::]:`

The PHP built-in development server is intended only for local testing; binding to all interfaces exposes the current working directory without authentication. The vulnerability is well-known and documented in the PHP manual.

The kata must **not** flag bindings that are explicitly local (e.g., `php -S 127.0.0.1:8000`). It should also ignore `php` commands that do not use the `-S` flag (e.g., `php artisan migrate`).

The warning message should clearly state the unsafe bind argument and recommend both using `127.0.0.1:PORT` for local development and placing a proper web server (such as nginx or Caddy) in front for any external exposure. No configuration option is needed for the kata itself beyond the standard kata disable mechanism supported by the tool.

feat: ZC1630 — flag `php -S 0.0.0.0:PORT` dev server on all interfaces (#649)


**Title:** New Kata ZC1732 – Warn when `flatpak override`/`flatpak run` escapes the Flatpak sandbox via broad `--filesystem` flags

### Problem / Use Case

Flatpak’s security model relies on filesystem sandboxing: applications can only see their own data and any paths the user explicitly chooses (usually through Filesystem portals). Using `flatpak override --filesystem=host` (or `flatpak run` with the same flag) grants the application **unrestricted read/write access to the entire host filesystem**, essentially dismantling the sandbox. The same risk applies to several other broad filesystem arguments:
- `--filesystem=home`
- `--filesystem=/`
- `--filesystem=host-os`
- `--filesystem=host-etc`
as well as the `:rw` variants of each.

ZShellCheck currently does not detect this dangerous pattern. Users (and CI) should be warned when a script uses such flags, because it is almost always a security mistake and a safer, more scoped alternative should be used.

### Expected Behavior

Add a new Kata (ID: **ZC1732**) that triggers on **any** `flatpak override` or `flatpak run` command where one of the arguments matches the broad filesystem specifiers listed above. The check should:
- Apply when the first argument is `override` or `run`.
- Flag any argument that is exactly one of the dangerous `--filesystem=…` strings (including `:rw` forms).
- Set the **severity to `warning`** (this is a risky security pattern).
- Report the violation at the position of the command itself (line/column where `flatpak` starts).
- Provide a descriptive message that mentions the subcommand and the flagged argument, explains why it removes sandbox isolation, and suggests granting a specific subdirectory (e.g. `--filesystem=~/Documents:ro`) or using Filesystem portals instead.

### Impact / Context

This check is modeled after similar “dangerous-command-flag” Katas already in the registry, and it should follow the same `SimpleCommand` hook pattern. The detection must be precise enough to **avoid flagging** valid uses such as `flatpak install` or `flatpak override --filesystem=~/Documents org.example.App` (which scopes to a specific directory). The check should fire even if the dangerous flag appears later in the argument list (e.g. after the application ID).

feat: ZC1732 — flag `flatpak override --filesystem=host` (sandbox break) (#752)


Add a new kata (ZC1798) that warns when `ufw reset` is used, because it wipes every user-defined firewall rule.  The warning should trigger for both `ufw reset` and `ufw reset --force`.  A known parser quirk causes `ufw --force reset` to be parsed as if the command were `force` with `reset` as the first argument; ensure the check handles that form too.  The message should advise snapshotting the rules first (`ufw status numbered > /tmp/ufw.bak`) and using `ufw delete <num>` for targeted removals.

feat: ZC1798 — warn on ufw reset wiping every firewall rule (#817)


Add a new check (ZC1706) to ZShellCheck that catches a common data‑corruption risk when using LVM’s `lvresize` or `lvreduce` without the filesystem‑resize flag.

**Acceptance criteria**

1. **For `lvreduce`** – always treat it as a shrinking operation.
   - Report an error when `lvreduce` appears in a simple command and neither `-r` nor `--resizefs` is present.

2. **For `lvresize`** – only treat it as shrinking when the size argument is explicitly negative.
   - When `lvresize` has a `-L` or `--size` option immediately followed by a value starting with `-` (e.g., `-L -2G`, `--size -100M`), consider it a shrink.
   - If the command is shrinking and neither `-r` nor `--resizefs` is present, report an error.
   - Do **not** flag `lvresize` when the size value starts with `+` or any non‑negative number (i.e., growing), even if `-r` is missing.
   - If `-r` or `--resizefs` appears anywhere among the arguments, no violation should be reported.

3. **Other commands**
   - `lvextend` must never trigger this check.
   - The check should only inspect simple commands whose name is exactly `lvresize` or `lvreduce`.

4. **Error severity**
   - The violation severity must be `error`.

5. **Error message**
   - The message must mention the actual command name that triggered the violation (`lvresize` or `lvreduce`).
   - It should explain that the LV is being shrunk without `-r / --resizefs`, that the filesystem is not shrunk first, and that writes past the new boundary will corrupt metadata.
   - It should suggest adding `-r` or manually shrinking the filesystem first.

**Additional guidance**
- The check must be registered with ID `ZC1706`.
- The implementation should follow the existing kata pattern (registration, check function, node type).
- A corresponding unit test should cover at least:
  - `lvresize` growing without `-r` → no violation
  - `lvresize` shrinking *with* `-r` → no violation
  - `lvresize` shrinking without `-r` → violation
  - `lvreduce` without `-r` → violation
  - `lvextend` → no violation

feat: ZC1706 — flag lvresize/lvreduce shrink without -r filesystem corruption (#726)


### Feature Request: Warn when glob expansion is disabled with `unsetopt GLOB` or `setopt NO_GLOB`

**Problem**
Disabling Zsh’s `GLOB` option (on by default) causes every subsequent unquoted `*`, `?`, `[...]`, and `**/` pattern to be passed literally to the command. A line like `rm *.log` then tries to remove a file named `*.log` instead of expanding. This leads to silent failures and surprising behaviour, e.g., `for f in *.txt` iterates once with the literal string `*.txt`. Developers often disable the option without realising the consequences.

**Existing Precedent**
ZShellCheck already has a check (ZC1830) that warns about `unsetopt NOMATCH`. A similar check targeting `GLOB` would be very helpful.

**Expected Behaviour**
A new Kata with ID `ZC1867` should report a warning when it encounters:
- `unsetopt GLOB`
- `setopt NO_GLOB`

These are equivalent mechanisms to turn off pattern expansion script-wide.

The check should **not** fire on:
- `setopt GLOB` (explicitly enabling the default)
- `unsetopt NOMATCH` or any other option

The warning message should explain the risk: `rm *.log` chases the literal `*.log`, `for f in *.txt` loops once, etc. It should suggest safer alternatives: quote the specific argument if a literal is needed, or scope the option change with `setopt LOCAL_OPTIONS; setopt NO_GLOB` inside a function.

**Severity**
This should be a **warning** (not an error), as it points out risky but sometimes intentional behaviour.

**Additional Notes**
- The option names are case-insensitive; underscores should be ignored (so `no_glob`, `NO_GLOB`, and `nog_lob` are all the same – but only the standard forms `unsetopt GLOB` and `setopt NO_GLOB` need to be matched in practice).
- The version string should be incremented to reflect the new Kata count (0.8.64 for 864 Katas).

Let me know if any clarifications are needed.

feat: ZC1867 — warn on `unsetopt GLOB` turning pattern expansion off script-wide (#888)


---
title: ZC1004 — missing detection for bare `exit` and missing auto-fix
labels: kata, enhancement

## Current behaviour

ZC1004 flags `exit <code>` inside a function body, but ignores a bare `exit` with no arguments.
Example:

```zsh
foo() {
  if [[ -z "$1" ]]; then
    exit 1   # correctly flagged
  fi
  exit       # not flagged
}
```

Additionally, ZC1004 does not provide an auto-fix; there is no `Fix` attached to the kata, so even flagged occurrences cannot be automatically rewritten to `return`.

## Desired behaviour

1. **Detection** — `exit` must be flagged in both of these forms when it appears inside a function body:
   - `exit <code>` (already works)
   - bare `exit` (missing)

2. **Auto-fix** — when a fix is applied (e.g. via `--fix` or the registry's `CheckAndFix` path), every flagged `exit` is rewritten to `return`. The exit-code argument (if present) and any surrounding whitespace must be preserved byte-for-byte; only the command name itself (`exit`) is replaced with `return`.

## Notes for implementation

- ZC1004 is defined in the kata registry under ID `"ZC1004"`. The check function and the kata `init` registration will need adjustments.
- The existing detection matches only `SimpleCommand` nodes whose name is `"exit"`. A bare `exit` with no arguments is represented by a different AST shape (the parser folds it into a plain identifier expression). The detection must handle that shape without double‑counting when the argument‑bearing shape is also matched.
- If a walker is involved, make sure the identifier inside a `SimpleCommand` is not visited a second time once the new identifier arm is added.
- The kata already defines a severity and message; those should be reused for both shapes.
- The auto-fix must be surfaced through the `Fix` field so that tools that consume the kata registry (the CLI and the integration tests) can use it.
- The fix should replace exactly the `exit` token (length 4) pointed to by the violation's line/column.

feat: ZC1004 Fix — exit to return (with arg-less detection) (#1067)


**Feature Request: New check ZC1883 – warn when `PATH_SCRIPT` is enabled via `setopt` or `unsetopt NO_PATH_SCRIPT`**

### Problem
Enabling the Zsh option `PATH_SCRIPT` (which is off by default) changes the behavior of the `.` and `source` builtins: when a literal path does not exist, they fall back to searching each entry in `$PATH`. A user who enables this option, either by writing `setopt PATH_SCRIPT` or the equivalent negation `unsetopt NO_PATH_SCRIPT`, may unknowingly open their shell to sourcing untrusted scripts from user‑writable directories (e.g., `~/bin` or `./`). The current tool does not flag these commands, leaving a dangerous misconfiguration undetected.

### Expected behavior
ZShellCheck should emit a **warning** whenever it encounters a command that enables `PATH_SCRIPT`:

- `setopt PATH_SCRIPT` (or any case/underscore variant that normalizes to `PATHSCRIPT`, e.g., `setopt path_script`)
- `unsetopt NO_PATH_SCRIPT` (or any variant that normalizes to `NOPATHSCRIPT`, e.g., `unsetopt no_path_script`)

The check must **not** warn when:
- The option is being **disabled** (e.g., `unsetopt PATH_SCRIPT` – the explicit default).
- An unrelated option is used (e.g., `setopt NOMATCH`).

The warning message should clearly explain the risk: that `.`/`source` will fall back to `$PATH` when a path misses, allowing a dropper script in `~/bin` or `.` to execute inside the current shell with full access to exported environment variables. It should recommend keeping the option off and using explicit paths.

### Severity / impact
This is a security‑sensitive misconfiguration that can lead to arbitrary code execution; I suggest a severity of **warning** (or higher if appropriate).

### Additional context
The new check can follow the existing kata registration pattern (a dedicated `ZC1883` kata). The implementation should handle normalized option names (uppercase, underscores stripped) so that every plausible spelling of the option is caught, regardless of capitalisation or extra underscores. The detection should operate on simple commands (i.e., `setopt` / `unsetopt` calls).

feat: ZC1883 — warn on `setopt PATH_SCRIPT` redirecting `source` via `$PATH` (#904)


## Feature Request: Add Kata ZC1557 – Flag dangerous `kubeadm reset -f` / `--force`

### Summary
`kubeadm reset` is a powerful command that tears down the Kubernetes control-plane and clears `/etc/kubernetes`. When used with `-f` or `--force`, it skips the interactive confirmation that normally protects against accidental execution. In CI pipelines, deployment scripts, or live environments, an unintended forced reset can instantly destroy a control-plane node and break the cluster.

We need a new **error-severity** kata that catches any Zsh line containing `kubeadm reset` immediately followed by `-f` or `--force`. The violation should warn the user and recommend draining/removing the node before running reset interactively.

### Desired behaviour
- **Trigger:** `kubeadm reset -f` or `kubeadm reset --force` (any order, but the flag must be on the same simple command).
- **Do NOT trigger on:**
  - `kubeadm reset` without a force flag.
  - Other `kubeadm` subcommands (`kubeadm init`, `kubeadm join`, etc.), even if they also carry `-f` (the check is specific to `reset`).
  - References to `kubeadm` that are not the command name (e.g., comments, strings).
- **Violation details:**
  - **Kata ID:** `ZC1557`
  - **Severity:** `error`
  - **Message:** `` `kubeadm reset -f` skips the confirmation and wipes /etc/kubernetes / kubelet state. Drain and remove the node first. ``
  - **Position:** line/column of the `kubeadm` command token.

### Implementation notes
- Register the kata against `ast.SimpleCommandNode` following the existing kata pattern (the init-time `RegisterKata` call). The check function can assert that the command’s name is `kubeadm`, the first argument is `reset`, and any remaining argument is `-f` or `--force`.
- Add thorough unit tests in the kata test suite (cover valid commands like `kubeadm init`, `kubeadm reset`, and invalid ones with both flag spellings). Use the standard `testutil.Check` and `testutil.AssertViolations` helpers already used by other kata tests.
- Bump the version constant in `pkg/version/version.go` to `"0.5.54"` to reflect the new kata count (current: `"0.5.53"` → 554 katas).

### Background
This check is analogous to other destructive‑command katas (e.g., `rm -rf` without safeguards). The goal is to make it impossible to accidentally destroy a Kubernetes control-plane from within a Zsh script without a deliberate, confirmed action.

Let me know if any edge cases need clarification.

feat: ZC1557 — error on kubeadm reset -f / --force (wipes k8s control-plane state) (#575)


Add a kata ZC1597 that detects `systemd-run -p User=root` and `systemd-run -p User=0` and reports a warning-level violation.  The check should understand the `systemd-run` command is launching a transient unit that runs as root, which bypasses the `sudo` audit path and can be used for privilege escalation.  Register the kata for simple commands and bump the version to account for the new rule.

feat: ZC1597 — flag `systemd-run -p User=root` transient-unit privilege escalation (#616)