Skip to main content

ocas-praxis

Praxis enhances behavioral refinement by recording outcomes, extracting lessons, and applying behavior shifts for continuous improvement.

Install this skill

or
45/100

Security score

The ocas-praxis skill was audited on Sep 29, 2026 and we found 3 security issues across 3 threat categories, including 1 critical. Review the findings below before installing.

Categories Tested

Security Issues

critical line 261

Eval function call

SourceSKILL.md
259- **Large gap backfill (80+ entries) is normal at steady-state** — cron pipelines write ~10 journals/minute. Between dispatch waves (7-8 min apart), expect 50-80 gap entries. This is expected, not a failure. See `references/session-20260629-dispatch-1030Z-praxis-second-wave-gap-backfill.md`
260- Cold-start: initialize state with CURRENT timestamp, not epoch
261- **Pure eval-registration dispatch (confirmed 2026-06-30T11:25Z):** When ALL `new_files` are already in praxis eval (just missing from dispatch eval) or are prior-wave artifacts, the Praxis pipeline does NOT need to run. Register directly from the dispatch pipeline, advance `last_ingest_run`, do NOT increment `journals_evaluated_count`. See `references/session-20260630-dispatch-1125Z-praxis.md`.
262- **CAPTURED_TS calibration (verified 2026-07-10, Mentor 2.8.23):** The light heartbeat did NOT advance `ingest_state.json:last_ingest_run` in this deployment. Before applying the CAPTURED_TS override, check `last_ingest_run` AFTER Mentor runs. If it is unchanged from the pre-Mentor value, run the ingest WITHOUT CAPTURED_TS — mtime discovery still finds the new journals (the override is only needed when the state timestamp actually moved forward). Applying CAPTURED_TS unnecessarily is harmless but adds an avoidable env-var step and a date-format footgun.
263- **No `praxis-dispatch` journal from the template (verified 2026-07-10):** `templates/dispatch_ingest_template.py` does not write a `praxis-dispatch-*.json` journal (unlike older production pipelines). The dispatch-output journals to bridge into the DISPATCH eval during third-wave mitigation are therefore: every journal the ingest just evaluated (all of them — the forge-scan output, the mentor-light heartbeat output, and any other cross-skill journals it registered) PLUS the `dispatch-wave-*` journal you write. Do NOT look for or fabricate a `praxis-dispatch` journal; bridge the full set of ingest-evaluated journal_ids instead.
high line 396

Urgency-based manipulation

SourceSKILL.md
394- **Pipe-to-interpreter commands hang autonomous cron jobs (security scan → `approval_pending`)** — Commands that pipe file contents into an interpreter (`cat file | python3 -c ...`, `tail -1 file | python3 ...`, `grep x file | python3 ...`) trip the environment's `tirith:pipe_to_interpreter` security scanner, which routes them to `approval_pending` status. A scheduled cron job has no user present to approve, so the command silently blocks and the run stalls (no error, no completion). **Fix (cron mode):** never pipe files into `python3`/`jq`/etc. Use (a) the dedicated `read_file` / `search_files` tools, (b) a plain `wc -l file` or `grep` terminal call with no pipe to an interpreter, or (c) `execute_code` with `from hermes_tools import read_file, terminal` when you must parse/transform content programmatically. Confirmed 2026-07-07: two `cat file | python3` and `tail file | python3` calls in this ingest both hit `approval_pending` and had to be rerouted to `read_file` + `execute_code`.
395
396- **`gap_backfill.py` crashed on explicit JSON `null` field values — `.get(k, '')` does not default a present-but-null key (confirmed 2026-09-27, FIXED at source)** — Several skills (notably `ocas-custodian` light-scans) emit `"not_activity_reason": null` rather than omitting the key. `data.get('not_activity_reason', '')` returns `None` in that case, because the default only applies when the key is ABSENT; the subsequent `nar.lower()` then raised `AttributeError: 'NoneType' object has no attribute 'lower'`. The crash happened inside the classify loop, so the backfill **aborted mid-loop** and silently left every remaining journal unbridged — the script still printed a partial summary, so the truncation was easy to miss. **Fix applied in `skills/ocas-praxis/scripts/gap_backfill.py`:** `nar = data.get('not_activity_reason') or ''` followed by a non-str coercion. **Rule for any `.get(k, '')` immediately followed by a string method:** use `data.get(k) or ''` (coerces `None` and `""` alike) and guard non-str types. **Symptom to recognize:** a `Traceback` inside `gap_backfill.py` combined with a later "Found 0 unevaluated" result on re-run means the first run died partway — re-run it after the fix, and verify the eval-file line count actually grew rather than trusting the printed summary.
397- **A 0-byte `commons/data/ocas-praxis/shifts.jsonl` is NOT live shift loss (confirmed 2026-09-27)** — Both praxis copies of `shifts.jsonl` have been 0 bytes since 2026-07-26, which reads as "no active shifts." It is not. That file holds the **legacy** schema (`id`/`shift_text`/`priority`/`reinforced`-style records). The live behavioral store is **`/root/indigokarasu/SOUL/autobio/shifts.jsonl`** (schema: `shift_id`/`kind`/`what`/`why`/`instances`/`status`/`lifecycle`) and it is what feeds the shift table in SOUL.md. Confirmed 17 live entries, current, containing exactly the shifts SOUL.md documents. **Do not "repair" the 0-byte praxis file by copying legacy content into it, and do not report it as data loss** — the schemas are unrelated and merging them would corrupt the live store. A stale copy of the live schema does exist at `/root/indigokarasu/SOUL.stray-*/autobio/shifts.jsonl` (13KB, Sep 22) if recovery is ever needed. Diagnose with `grep -c '"priority":' <file>`: `0` means autobio-schema (live), `>0` means legacy.
398- **Re-read `ingest_state.json` AFTER `gap_backfill.py` before your state update** — `gap_backfill.py` writes its own fields back to the state file (`eval_lines`, and increments `eval_gaps_backfilled`). If you snapshot the state once at the start of the run and later apply your load→modify→dump update, you will clobber gap backfill's writes. **Fix:** re-read `ingest_state.json` immediately before the final state update so your changes compose on top of the backfill's counters. Confirmed 2026-07-07: gap backfill advanced `journals_evaluated_count` 49232→49240 between the initial read and the update step; re-reading preserved it. (Pairs with the load→modify→dump pattern — never hand-type the JSON body, which risks dropping nested keys like `stale_script_cleanup`.)
high line 396

Fetch and execute pattern

SourceSKILL.md
394- **Pipe-to-interpreter commands hang autonomous cron jobs (security scan → `approval_pending`)** — Commands that pipe file contents into an interpreter (`cat file | python3 -c ...`, `tail -1 file | python3 ...`, `grep x file | python3 ...`) trip the environment's `tirith:pipe_to_interpreter` security scanner, which routes them to `approval_pending` status. A scheduled cron job has no user present to approve, so the command silently blocks and the run stalls (no error, no completion). **Fix (cron mode):** never pipe files into `python3`/`jq`/etc. Use (a) the dedicated `read_file` / `search_files` tools, (b) a plain `wc -l file` or `grep` terminal call with no pipe to an interpreter, or (c) `execute_code` with `from hermes_tools import read_file, terminal` when you must parse/transform content programmatically. Confirmed 2026-07-07: two `cat file | python3` and `tail file | python3` calls in this ingest both hit `approval_pending` and had to be rerouted to `read_file` + `execute_code`.
395
396- **`gap_backfill.py` crashed on explicit JSON `null` field values — `.get(k, '')` does not default a present-but-null key (confirmed 2026-09-27, FIXED at source)** — Several skills (notably `ocas-custodian` light-scans) emit `"not_activity_reason": null` rather than omitting the key. `data.get('not_activity_reason', '')` returns `None` in that case, because the default only applies when the key is ABSENT; the subsequent `nar.lower()` then raised `AttributeError: 'NoneType' object has no attribute 'lower'`. The crash happened inside the classify loop, so the backfill **aborted mid-loop** and silently left every remaining journal unbridged — the script still printed a partial summary, so the truncation was easy to miss. **Fix applied in `skills/ocas-praxis/scripts/gap_backfill.py`:** `nar = data.get('not_activity_reason') or ''` followed by a non-str coercion. **Rule for any `.get(k, '')` immediately followed by a string method:** use `data.get(k) or ''` (coerces `None` and `""` alike) and guard non-str types. **Symptom to recognize:** a `Traceback` inside `gap_backfill.py` combined with a later "Found 0 unevaluated" result on re-run means the first run died partway — re-run it after the fix, and verify the eval-file line count actually grew rather than trusting the printed summary.
397- **A 0-byte `commons/data/ocas-praxis/shifts.jsonl` is NOT live shift loss (confirmed 2026-09-27)** — Both praxis copies of `shifts.jsonl` have been 0 bytes since 2026-07-26, which reads as "no active shifts." It is not. That file holds the **legacy** schema (`id`/`shift_text`/`priority`/`reinforced`-style records). The live behavioral store is **`/root/indigokarasu/SOUL/autobio/shifts.jsonl`** (schema: `shift_id`/`kind`/`what`/`why`/`instances`/`status`/`lifecycle`) and it is what feeds the shift table in SOUL.md. Confirmed 17 live entries, current, containing exactly the shifts SOUL.md documents. **Do not "repair" the 0-byte praxis file by copying legacy content into it, and do not report it as data loss** — the schemas are unrelated and merging them would corrupt the live store. A stale copy of the live schema does exist at `/root/indigokarasu/SOUL.stray-*/autobio/shifts.jsonl` (13KB, Sep 22) if recovery is ever needed. Diagnose with `grep -c '"priority":' <file>`: `0` means autobio-schema (live), `>0` means legacy.
398- **Re-read `ingest_state.json` AFTER `gap_backfill.py` before your state update** — `gap_backfill.py` writes its own fields back to the state file (`eval_lines`, and increments `eval_gaps_backfilled`). If you snapshot the state once at the start of the run and later apply your load→modify→dump update, you will clobber gap backfill's writes. **Fix:** re-read `ingest_state.json` immediately before the final state update so your changes compose on top of the backfill's counters. Confirmed 2026-07-07: gap backfill advanced `journals_evaluated_count` 49232→49240 between the initial read and the update step; re-reading preserved it. (Pairs with the load→modify→dump pattern — never hand-type the JSON body, which risks dropping nested keys like `stale_script_cleanup`.)
Scanned on Sep 29, 2026
View Security Dashboard
Installation guide →
Rating
5.01
Rate this skill
Categoryanalytics
UpdatedOctober 9, 2026
indigokarasu/praxis