AI Coding
Overview (2026-10-07)
Section titled “Overview (2026-10-07)”AI Coding checks every line a coding agent adds — in Claude Code, through the Logrite
plugin — against the workspace’s compliance frameworks, logging standards and business rules,
on the developer’s machine. When the agent finishes a turn, the plugin hands it the issues in the
files it changed and asks it to fix them before it stops. What happened is recorded as session
metadata and masked rule findings, rolled up into issues on the AI Coding page
(/ai-coding, in the sidebar right after AI Warden).
It is notify-only. No edit, turn, commit or push is ever blocked: the end-of-turn fix round is a request to the agent with a hard cap, the commit check only warns, and every hook exits 0 when anything goes wrong.
| Piece | Where it runs | What it does |
|---|---|---|
Plugin hooks (plugin/hooks/hooks.json → lr.sh → lr_hook.py) | Developer machine, python3 3.8+, stdlib only | Scan added lines, fix round at Stop, spool and ship events |
POST /coding/events, GET /coding/policy | MCP server (https://mcp.logrite.ai) | Token-authenticated ingest and policy — see MCP Server → Session-ingest routes |
coding-events- per-workspace index, coding_issue tables | Logrite backend | Events, issue roll-up, retention |
POST /coding/describe | Analyzer | The AI write-up of each new issue |
/ai-coding page, Settings → AI coding | Console | Triage; workspace switches |
Setup on each developer machine is two steps: install the plugin, then run /logrite:init in
each repository.
curl -fsSL https://mcp.logrite.ai/install.sh | shThe installer has three modes:
| Mode | How | What it registers |
|---|---|---|
| Key from the environment | curl -fsSL https://mcp.logrite.ai/install.sh | LOGRITE_API_KEY=lgp_live_… sh (the variable goes on sh, not on curl; export LOGRITE_API_KEY first also works) | the key is written to ~/.logrite/api-key (mode 0600) and read by the headersHelper ~/.logrite/mcp-headers.sh |
| Hidden prompt | no variable set: it asks for the key on the terminal with echo off | same as above |
| OAuth | press Enter at the prompt, or run with no terminal and no variable | claude mcp add -s user --transport http logrite <mcp>/sse with no header; then run /mcp, choose logrite, and Authenticate |
The key never appears in a command line or in Claude Code’s MCP config. Every mode installs the
plugin (logrite@logrite-local, user scope). There is no positional key argument. See
MCP Server → The Claude Code plugin.
What is recorded (2026-10-07)
Section titled “What is recorded (2026-10-07)”Never recorded, at any capture level: prompt text, the agent’s replies, tool inputs or outputs,
and source code beyond the masked ≤200-character snippet of a line that broke a rule. Token and
tool counts are read from the Claude Code transcript (lr_transcript.py), which extracts tool
names, tool-error counts, token usage and the model id — never content. The developer is stored
only as actorHash = sha256(workspaceId + "|" + actor id), where the actor id is a random UUID
created on first use in ~/.logrite/actor (mode 0600) and never derived from the git identity.
The branch is stored only as branchHash = sha256(branch name).
Event types
Section titled “Event types”Each event is one NDJSON line in coding-events- plus the workspace id. Fields every event
carries: eventType, @timestamp, sessionId, promptSeq, client (name, version),
plugin.version, repo (the label /logrite:init wrote), captureLevel, and when known
projectId, model, branchHash, actorHash.
eventType | Sent when | Extra fields |
|---|---|---|
session_start | SessionStart, recording on | — |
turn | Stop, once the turn is final | tools (counts by tool name), toolErrors, tokens (input, output, cacheRead), filesChanged, linesAdded, linesRemoved, durationMs |
finding | Stop (once per finding, after any fix round) | finding — see below |
heal | Stop after a fix round | heal (round, findings, resolved) |
commit_scan | Warn-only pre-commit | commit.findings (a count); client.name is git-pre-commit |
session_end | SessionEnd | durationMs |
Capture levels (coding.capture)
Section titled “Capture levels (coding.capture)”Set in Settings → AI coding (default findings). The MCP server enforces it on ingest: the
stored level is the lower of the workspace’s setting and what the plugin says it captured, so
a stale plugin cannot record more than the workspace allows.
| Level | finding carries |
|---|---|
metadata | fingerprint, ruleId, source, severity, framework, line, ext, fixedInSession, and pathHash = sha256(path). No message, snippet or path — the server drops them whatever the plugin sent |
findings | All of the above plus message (≤300 chars), path (≤300) and a masked snippet (≤200) |
extended | Today the same as findings. Neither the plugin (lr_common.shape_finding) nor the MCP server (coding_ingest._finding) treats it differently |
The session-ingest token (2026-10-07)
Section titled “The session-ingest token (2026-10-07)”The plugin sends events with a write-only lgsi_live_ token, bound to one workspace.
- Minted by
/logrite:init, through the MCP toolcoding_sessionswith actionenrolandinclude_token: true(without that flag the token is withheld from the tool result). - Stored outside the repository at
~/.config/logrite/coding/<workspaceId>.token, mode0600. The environment variableLOGRITE_CODING_TOKENoverrides the file. It is never written to.logrite/config.json, never into the repository, never echoed. - Accepted only by
POST /coding/eventsandGET /coding/policyon the MCP server. It is not an API key:/sseand every REST route refuse it, and those two routes refuse anlgp_live_key with 401. - A revoked token stops working within the MCP server’s 60-second resolve cache.
Route (/api/v1/coding, workspaceId query param) | Who | Result |
|---|---|---|
POST /tokens body {label} | Any workspace member (login, API key, or the MCP connection) | {id, token, last4, label, createdAt} — the plaintext once |
GET /tokens | Any member | {tokens:[{id, label, last4, createdBy, createdAt, lastUsedAt, revokedAt}]} — never the token or its hash |
DELETE /tokens/{id} | Workspace admin, or whoever minted it | 204; another workspace’s id → 404 |
POST /enrol body {label, frameworks} | Any member | {tokenId, token, last4, policyCreated, policyId} |
label defaults to AI coding (max 255 characters). Mint and revoke are written to the
governance audit as coding_token_minted / coding_token_revoked. enrol also makes sure the
workspace has a business-rules policy for AI coding (CodingEnrolService.ensureCodingPolicy): it
returns the first non-Off business_rules policy whose features include ai_coding; else adds
ai_coding to an existing policy named “AI coding (default)”; else creates that policy
(workspace scope, On (enforcing), async_observe, fail_open, one framework item per repository
framework, origin system). An existing policy’s rules are never rewritten. An unknown framework is
a 400 and no token is minted; all 30 library frameworks are accepted (FrameworkCatalog). Until
2026-10-15 the response said ruleSetCreated / ruleSetId.
The hooks (2026-10-07)
Section titled “The hooks (2026-10-07)”Every hook runs lr.sh <event> → lr_hook.py, one Python process per event (plugin 1.2.0
folds the former shell hooks into it). No python3 → lr.sh exits 0 silently. Every handler
runs inside lr_common.run, which swallows every exception and exits 0
(LOGRITE_HOOK_DEBUG=1 prints the traceback). Only repositories with a .logrite/config.json
holding a workspaceId are touched at all, and nothing is written inside the repository — state
lives under ~/.logrite/ (spool, cache, actor id).
| Hook | Timeout | What it does |
|---|---|---|
SessionStart | 5 s (+ async flush) | Refreshes the policy if the cached one is older than 1 hour (2 s budget), whether or not the repository records sessions; spools session_start; adds the notice below to the session |
UserPromptSubmit | 5 s (+ async count) | Counts the prompt (opens a new turn). When the prompt looks like an error report and the repository’s config lists add-ons, points the agent at knowledge_search. The prompt text is never stored or sent |
PostToolUse matcher Write|Edit|MultiEdit | 10 s | Scans the lines added (since the last commit) to the edited file; muted issues dropped; a finding already reported this session for the same (fingerprint, path, line text) is not repeated; up to 5 findings returned to the agent as context, ≤1500 characters |
Stop | 30 s (+ async flush) | Records the turn; may run a fix round (below) |
SessionEnd | 2 s | Spool only — no network in the SessionEnd budget |
The SessionStart notice, shown in every recorded session:
Logrite records this session for compliance: metadata and masked rule findings only — no prompts or code. Opt out for this repo with “recordSessions”: false in .logrite/config.json.
The PostToolUse context starts Logrite found N issue(s) in lines just added to <path>:
followed by numbered lines path:line message — fix hint, worst first.
The fix round at Stop
Section titled “The fix round at Stop”At Stop the plugin scans the lines added to every file the turn touched — including files changed through Bash (since plugin 1.1.3: git-modified or untracked files whose mtime is at or after the turn’s start). It sends the agent back only when all of these hold:
coding.healis on (default on);- the turn has had fewer than
coding.healRoundsrounds (0–2, default 1); - Claude Code’s
stop_hook_activeis false — so a finding the agent cannot fix can never trap the session in a loop; - at least one finding is at or above
coding.healMinSeverity(defaultmedium) and not muted.
The block reason is capped at 4000 characters, lists at most 10 findings and at most 5 of the coding plane’s natural-language policies:
Logrite found 3 issue(s) in files you changed. Fix them, then finish:1. src/Payment.java:42 A log statement may carry a credential — Remove the credential from the log call; log a non-secret id instead.2. …Workspace policies to respect:- <policy text>A healed finding is recorded as found, then fixed (since plugin 1.1.2): the finding is spooled
with fixedInSession: false when the round is raised, and again with fixedInSession: true once
the next Stop’s re-scan no longer sees it, together with a heal event
({round, findings, resolved}). A fixedInSession sighting is not a new occurrence on the server.
The fix-round counter lives in ~/.logrite/cache/<session>.heal.
Measured on dev (2026-10-07, a real claude -p session in a clone of a Java test repository):
Stop blocked once naming the finding, the agent fixed it, and coding-events-3103 received
finding (fixedInSession: false) → finding (fixedInSession: true) + heal
{round: 1, resolved: 1} — with no prompt text, no full source line, and the snippet masked
(password=***).
Spool and flush
Section titled “Spool and flush”Events are appended to ~/.logrite/spool/<workspaceId>/events.jsonl and shipped by the async
flush hook after SessionStart and Stop: gzip NDJSON batches of at most 500 events or
256 KB to POST {mcpUrl}/coding/events, 5-second timeout. A batch answered 400/413/422 is
dropped (the server will never take it); offline, 401, 429 and 5xx keep it for the next flush.
The spool is capped at 5 MB: past that, the oldest non-finding events are dropped first,
findings last. Session state older than 7 days is pruned.
Policy refresh
Section titled “Policy refresh”The policy comes from GET {mcpUrl}/coding/policy and is cached in
~/.logrite/cache/<workspaceId>/policy-<projectId or "ws">.json with its ETag (since 2026-10-15 the
fetch is GET {mcpUrl}/coding/policy?projectId=<projectId> when .logrite/config.json has a
projectId, and one cache file is kept per project), so an unchanged refresh costs one
304. It is refreshed at SessionStart (when older than 1 hour) and by scan --worktree (when
older than 5 minutes); PostToolUse, Stop and the pre-commit check use the cached copy. A failed
refresh writes policy.failed next to the cache, and no refresh is tried for 10 minutes, so an
offline machine pays the 2-second budget once rather than at every session start. With no
policy cached, the plugin falls back to built-in copies of the logging-standard rules.
Warn-only pre-commit check (2026-10-07)
Section titled “Warn-only pre-commit check (2026-10-07)”Installed only when the developer says yes at /logrite:init
(lr_hook.py precommit install):
- The hook file is
$(git config core.hooksPath)/pre-commitwhen that is set, else.git/hooks/pre-commit. The check itself (logrite_precommit.pyand its scanner copylogrite_scan.py) always goes to.git/hooks/— never into a committed hooks directory. - An existing shell hook keeps all of its content: a marked block is inserted right after its shebang. A non-shell hook is left untouched and the check is not added. Re-running replaces the block in place.
# >>> logrite (warn-only) >>># Logrite: warns about issues in the staged lines. Never blocks the commit.if command -v python3 >/dev/null 2>&1 && [ -f '…/.git/hooks/logrite_precommit.py' ]; then python3 '…/.git/hooks/logrite_precommit.py' || true; fi# <<< logrite <<<It scans the staged lines with the cached policy, drops muted issues, and prints to stderr:
Logrite (warning only — this commit will proceed): 2 issue(s)1. …It always exits 0, never edits a file and never re-stages anything. It spools one
commit_scan event (a count only) unless recording is off. coding.precommit = false makes it
do nothing. /logrite:uninstall removes exactly the marked block (and the hook file only if the
block was all it held) and deletes the two scripts.
Measured on dev (2026-10-07): the hook printed its warning and the commit exited 0.
Rules applied (2026-10-07)
Section titled “Rules applied (2026-10-07)”The coding policy (GET /internal/coding/policy?workspaceId=&projectId=, served to the plugin as
GET /coding/policy[?projectId=]; the MCP server caches per (workspace, projectId) and ignores a
non-numeric projectId) is built from the effective ai_coding policies of
Rules & Policies: workspace-wide ones, plus project-scoped ones when the
projectId is a project of the workspace. It
carries settings (every coding.* and logging.* key), the redaction list, rules,
nlPolicies, and muted (fingerprints of issues muted now). version is 16 hex of a SHA-256
over the canonical body, and is the ETag. Only added lines are scanned — one finding per
rule per line. Each rule family has its own switch in Settings → AI coding.
source | Switch | Rules |
|---|---|---|
compliance | coding.scan.compliance | The compliance library’s value patterns for the frameworks of the workspace’s effective AI-coding policies — only the data-exposure categories (pii, phi, pci, credential, request_body). Rule id compliance.<rule> (.1, .2 … when a rule has several patterns); message <FRAMEWORK>: personal data written into code (or health information / payment card data / a credential / sensitive content); fix hint = the library’s remediation text |
logging | coding.scan.logging | The three logging standards below |
business | coding.scan.business | Enabled content_regex / regex items of effective AI-coding policies whose “Run these rules” (deterministicRules) is not false — a missing value counts as on, as in the editor (backend, 2026-10-19; before, a missing value read as off) — id business.rule-<ruleKey> (migrated rules keep the old rule id as key, so fingerprints and mutes still match), message Code matches a rule in '<policy>'; and their plain-English (ai_judge) rules, handed to the agent in the Stop reason |
Logging standards (the plugin’s offline fallback uses identical ids and messages, so fingerprints match):
ruleId | Severity | Message | Present when |
|---|---|---|---|
logging.secret-in-log | high | A log statement may carry a credential | A log call passes a credential value (see below) |
logging.pii-in-log | medium | A log statement names a person rather than an id | logging.piiFields is not empty (default email, ssn, password, phone, dob, cardNumber) and a log call interpolates one of those fields (see below) |
logging.missing-correlation-field | low | A log statement omits the required correlation field | logging.correlationField is set (default empty = not required); debug lines exempt |
logging.secret-in-log flags a logged credential VALUE, not a word (plugin 1.3.3, backend
581b4964; both carry the identical pattern). It fires when a log/logger/logging call —
including log[level](…) — passes an identifier whose last segment ends in a secret word
(token, accessToken, self._token, getPassword(), apiKey, authorization, credentials)
or a lookup by a secret name (req.get("Authorization"), os.environ["API_KEY"]), as an argument,
after , + % : =, inside ${…} / {…} or through .format(. It does not fire on a key
(token: x passes x), on a word in the message ("token expired"), on a derived name
(token_sha256, tokenFingerprint), or on a value passed through a mask, hash, redact, digest,
fingerprint, sha-N, hmac or len helper — so logging a hash of the credential, which the fix hint
asks for, now heals the finding.
Source-code forms of two framework rules (backend 581b4964). On the AI-coding plane a word
is not a finding, a value is: the SOC2 credential rule (compliance.soc2_log_leak_credentials.4)
needs a quoted literal that is not placeholder-shaped (test-token-123, changeme) or an
unquoted value with a digit that is not a call or a dotted reference — so accessToken: string,
function bearerAuth(token: string) and self._token = settings.fhir_gateway_token no longer
fire. The HIPAA, BIPA and US-state biometric rules need the biometric word as a name with biometric
data assigned, not the “fingerprint” of a token. Prompts, responses and logs keep the original
value patterns. The catalogue carries these as codePatterns (replaces: <n>), served only when
the library rule is unedited.
References to a secret are not credentials (plugin 1.1.4). A line that names a secret without
holding it — ${DB_PASSWORD}, os.getenv(…), a template {{ … }}, or an already-masked *** —
is not a credential finding. Inside a log call only masks (***) are exempt: interpolation
there inserts the real value into the log. ${NAME:default} with a non-empty default is still a
finding, because the default is a literal value.
logging.pii-in-log flags only an interpolated field value (plugin 1.2.0 and the backend
use the same pattern). It fires when a log/logger/logging .info/.warn/.error/.debug call
passes the field’s value: as the first argument, after , + or %, as ${email}, $email or
{email}, through .format(email), or qualified/wrapped (user.getEmail(), str(email)). It
does not fire on a literal (log.info("email=j***@acme.com")), on a field name followed by a
{} placeholder whose argument is something else, or when the value passes through a wrapper
whose name contains mask, hash or redact (maskEmail(email)). Logging userId next to the
field does not exempt the line. The fix hint: log the person’s id, or pass the value through a
mask, hash or redact helper. The offline fallback uses the server’s default field list.
Snippets are masked by field name too (email=***), but a {} / {0} placeholder after the
field name is not a value and is kept as written (plugin 1.1.5): before it, an SLF4J call was
recorded as email=***}.
Each finding carries a fix hint (fixHint, plus Suggested replacement: … when the rule
has a fix.replace), shown to the agent and in the pre-commit warning.
Issues (2026-10-07)
Section titled “Issues (2026-10-07)”Findings roll up into issues per workspace by fingerprint = sha256(ruleId + "|" + message),
first 32 hex, lower-case. The backend always recomputes it; a client-supplied fingerprint is
never read. At capture level metadata the MCP server still strips message before forwarding,
but a coding finding’s message is the rule’s own text from the served policy, never user
content — so the backend puts it back from the workspace’s policy (CodingIssueService) before
recomputing. Mutes and roll-ups therefore match the plugin’s local fingerprints at every capture
level, and the issue is titled by the rule message. A rule the policy no longer serves keeps its
missing message and rolls up under sha256(ruleId + "|"), titled by its rule id.
- Counters.
occurrences(sightings),sessionCount(distinct sessions),fixedInSessionCount,firstSeen,lastSeen. AfixedInSessionsighting is not a new occurrence and never reopens anything. The newest 10 evidence rows (session, path, line, extension, masked snippet, time) are kept per issue. - Statuses
OPEN,MUTED(optionally untilmutedUntil),FIXED.- A
FIXEDissue seen again after it was marked fixed reopens asOPENwithregressed: true— shown as “Came back”. - A mute that has expired drops out of the policy’s
mutedlist at once; the issue reopens the next time it is seen. - A person setting the status clears
regressed.
- A
- Triage (
PATCH) needs thecoding-triagerole floor — Maintainer by default — because muting an issue silences it in every developer’s fix round.
AI write-up
Section titled “AI write-up”Each new issue (and a regressed one without a write-up) is described once by the analyzer’s
POST /coding/describe, on the flex lane of the pinned model: a title (≤80 characters), a
description (≤400) and a proposed fix (≤1500, markdown, one fenced code block). The request
carries the rule, severity, framework, message, file extension, counts and up to five masked
snippets — never a path. Until it lands the issue shows the fallback: title = the rule message
(restored from the policy at capture level metadata; the rule id only when the policy no longer
serves that rule), description = message plus framework. A failed or
unusable reply is retried by an hourly sweep, at most 3 attempts in total.
Notifications
Section titled “Notifications”Only new and regressed issues at or above coding.notifyMinSeverity (default high)
are announced. Neither channel carries a snippet, a path or who was coding.
| Channel | Gate | Content |
|---|---|---|
Discord, category “AI coding sessions” (coding) | Connection on and the category ticked | Title (prefixed Came back: for a regression), Severity, Type, Seen N times, link to /ai-coding?issue=…; footer Logrite AI coding · opening the issue needs a Logrite login |
Bell, type CODING_ISSUE (filter label “AI coding”) | notify.coding.bell (default on) | New AI coding issue: <title> or AI coding issue came back: <title>; body <severity> · <type> · seen N times; opens /ai-coding?issue=… |
Measured on dev (2026-10-07): an issue marked fixed, then re-introduced by the same rule in a
later session, reopened as OPEN with regressed: true (“Came back”); the bell showed
“AI coding issue came back: …” (CODING_ISSUE, opening /ai-coding?issue=2) and the Discord
message was delivered (Discord answered 204).
Public read API
Section titled “Public read API”All under /api/v1/coding, workspaceId query parameter on every route; reads are open to any
workspace member with a login or an API key (the MCP coding_sessions tool uses these).
| Route | Returns |
|---|---|
GET /summary?from&to&tz | {sessions, turns, commitsChecked, issuesOpen, fixedInSessionPct, byType:[{source,count}], overTime:[{date, compliance, logging, business, secret}]}; default window 7 days; tz an IANA zone for the per-day series |
GET /issues?status&source&severity&q&from&to&page&size | {items, total, page, size} — newest lastSeen first; size default 25, max 100; q matches title, message or rule id |
GET /issues/{id} | {issue, evidence, series (30 days), captureLevel}; another workspace’s id → 404 |
PATCH /issues/{id} body {status, mutedUntil?} | The issue; 400 for an unknown status or a mutedUntil not in the future; 403 below the triage floor |
POST /issues/{id}/pull-request | {status: "opened" | "no_changes" | "failed", prUrl?, message?} via Cortex Fix-it (at most 5 files); 409 {"error":"no_project"} when the issue’s projectId is not a project of this workspace |
GET /sessions?from&to&page&size | {items:[{sessionId, startedAt, endedAt, turns, filesChanged, findings, healed, repo}], total} — size default 50, max 200. No person column, by design |
The summary was checked live on dev: sessions, turns, fixedInSessionPct, byType counted by
source (compliance, logging, business, secret) and overTime per day in the requested
time zone.
Storage. The coding-events-* index template maps the finding and heal objects as
flat_object. A coding query that OpenSearch refuses answers 503, not 500. The backend
authenticates to the analyzer with its token when it asks for an AI write-up (51287ec4).
Retention
Section titled “Retention”coding.retentionDays (7–90, default 30). A nightly job (cron 0 30 3 * * *, 03:30 server
time) deletes events older than that from each workspace’s coding-events- index and prunes
evidence rows (masked snippets, paths) past the same horizon. Issue rows and their counters are
kept — the mute and fixed decisions are the workspace’s, and losing them would reopen every
muted issue the next time it is seen.
The AI Coding page (2026-10-07)
Section titled “The AI Coding page (2026-10-07)”AI Coding (/ai-coding), in the sidebar right after AI Warden. The selected issue is in the
URL as ?issue=…, so a bell or Discord link opens it directly.
Loading (2026-10-15, Logrite-UI 966cff9c). Until the workspace’s first answer arrives, the
page is only the Logrite loader (<LogriteLoader size="xl" />, test id
ai-coding-page-loading), as on the other pages. Before this change it drew zeroed tiles and an
empty list first, then swapped to the data or to the empty state. It also showed them again while an
empty window stepped out to a wider one. The loader stays up through that widening. An error ends
it, so the error message shows rather than a loader that never stops, and switching workspace
brings it back until the new workspace answers. Later filter, window and Live refreshes keep the page
and show loading only in the list.
- Title “AI Coding”; subtitle “Every AI coding session, checked against your compliance, logging and business rules. Issues get fixed while the agent is still working — nothing is ever blocked.”
- Live switch (on by default), tooltip “Refreshes every 15 seconds”: a quiet refresh every 15 s while the tab is visible; a hidden tab pauses it. Beside it, the time range (default last 7 days). An empty preset window steps out to the next wider one and says so: “Nothing in the last 7 days — showing the last 30 days.”
- KPI tiles: Sessions, Open issues, Fixed in-session (percentage, “of issues found while coding”), Commits checked.
- Charts: Issues by type (donut) and Issues over time (stacked bars, “per day, in your
time zone”). Type colours: Compliance
#F79009, Logging standards#53B1FD, Business rules#A78BFA, Secrets#F04438. - Tabs Issues and Sessions.
Issues tab
Section titled “Issues tab”Detected issues (N) with a search box and filters (All statuses / All types / All
severities). Each row: a dot in the type colour, the title, one chip — Open, Came back,
Muted / Muted until <date>, or Fixed — and “<severity> · Seen in N sessions · last
<time> ago”.
Selecting an issue opens its detail: title, chip, “N occurrences · N sessions · since
<date>”, and three tabs:
- Overview — the description, Rule, Framework, Severity, Type, First seen, Last seen, and a 30-day sparkline. While the write-up is pending: “AI write-up in progress — showing the rule details meanwhile.”
- Evidence —
path:line, time, a fixed in session chip, and the masked snippet. At capture levelmetadata: “Snippets aren’t recorded at your capture level. Change it in Settings → AI coding.” - Proposed fix — the fix with Copy fix.
Status actions: Mute opens a menu headed “Mutes this rule in every repository in this
workspace.” — For 7 days, For 30 days, Until I unmute; Mark as
fixed toasts “Marked fixed — we’ll reopen it if it comes back”. A muted or fixed issue offers
Unmute / Reopen instead. A role below the floor gets “Your role can’t change issue status
in this workspace.” Muting is audited as coding_issue_muted; unmuting (muted → any other status)
as coding_issue_unmuted since 2026-10-19, so Rules & Policies → Activity shows both.
Next steps:
| Button | Does |
|---|---|
| Copy issue details | Markdown: title, rule, type, severity, framework, counts, description, affected path:line, proposed fix — never snippets |
| Create rule | Opens the shared policy editor drawer with a new business-rules policy (feature AI coding, workspace scope, On) holding the issue text as a plain-English rule with action Flag. Saving toasts “Policy saved. Find it in Rules & Policies → Policies.” |
| Fix in Claude Code | Copies /logrite:fix <id> — toast “Copied — paste it into Claude Code” |
| Open pull request | Cortex Fix-it opens a PR. Disabled unless the issue has a project, with the tooltip “Link this repo to a Logrite project to open pull requests” |
Sessions tab
Section titled “Sessions tab”Columns Started, Repo, Turns, Files changed, Findings, Fixed in-session — deliberately no person column.
Empty state and footer
Section titled “Empty state and footer”With no session ever recorded: “No AI coding sessions yet” and three steps — Install the Logrite plugin (the installer line with a copy button), Run /logrite:init in your repo, Code as usual — issues appear here within a minute — then “Waiting for your first session…”.
Footer on every view: “Recorded: session metadata and masked rule findings. Not recorded: prompts, code, or who typed what.”
Plugin commands (2026-10-07)
Section titled “Plugin commands (2026-10-07)”/logrite:init
Section titled “/logrite:init”Each step is idempotent: running it again changes nothing.
- Calls
get_workspace_context; if it fails, stops and says to re-run the installer or run/mcp→ Authenticate. - Finds the remote (
origin, else the only remote) and uses the directory name as the label. - Calls
enrol_repositorywithremoteUrl. With no remote, or when enrol answers 400 or 409 (typically: the Logrite GitHub App is not connected to this workspace), it continues local-only with"projectId": null: local checks run, but server scans and pull-request checks need the GitHub App — connect it in Settings → Workspace → Source control → Link existing installation, then run/logrite:initagain. - Uses
enabledFrameworksfrom step 1 (frameworks enabled for AI coding, K1); only when that is empty does it ask one multi-select oflist_compliance_frameworks, SOC2 preselected.addonsfromworkspace_addons list. - Writes
.logrite/config.json(workspaceId,workspaceName— omitted when unknown, never the id —projectId,label,frameworks,addons,mcpUrl). No key or token goes in it; it is meant to be committed. - Asks one question: record AI coding sessions, add the warn-only pre-commit check, both
(default) or neither. Without python3 it says “Recording needs python3” and sets
"recordSessions": false. Whatever the answer, it runslr_hook.py token check --workspace <id> --mcp-url <url>; onmint …it callscoding_sessionsenroland stores the token withlr_hook.py token claim(see Recording-token claim codes). The model never sees the token. - Reports what it wrote and says: “Edits and commits are never blocked. Before it finishes, the agent is asked once to fix what Logrite found.”
Without a question tool (claude -p), init never stops to ask: it applies the defaults and begins
its report “Non-interactive run: applied the defaults — frameworks …, recording on, pre-commit
check on. Run /logrite:init in an interactive session to change them.” If the workspace has the
worksoft-cortex add-on but the Worksoft skill is not installed, it says “Re-run the installer to
add the Worksoft skill.”
/logrite:fix <id>
Section titled “/logrite:fix <id>”Fetches the issue with coding_sessions issue, applies the proposed fix to the evidence paths
in the working tree (never reconstructing masked values), re-scans with
lr_hook.py scan --worktree, and reports. It does not commit, push, open a PR, or mark the issue
fixed.
/logrite:privacy
Section titled “/logrite:privacy”Reports, from the machine’s actual state, whether the repository is recorded, the capture level from the cached policy, what each level records, what is never recorded, the size of the unsent spool, and how to opt out. It never prints the token or spooled content.
/logrite:compliance-check
Section titled “/logrite:compliance-check”First runs lr_hook.py scan --worktree locally — lines added since the last commit, staged,
unstaged and untracked, against the workspace’s coding rules; no code leaves the machine. Then it
offers run_compliance_scan on the pushed branch, which the server clones.
Privacy (2026-10-07)
Section titled “Privacy (2026-10-07)”- Metadata and masked findings by default (
coding.capture=findings);metadataremoves messages, paths and snippets. Never prompts, replies or code. - Visible: every recorded session opens with the notice above, and
/logrite:privacyshows exactly what is recorded on that machine. - Opt out per repository with
"recordSessions": falsein.logrite/config.json— no events are recorded or sent, and the notice is not shown. The local checks (PostToolUse context, the Stop fix round) still run. A workspace admin can switch recording off for the whole workspace (coding.enabled): the MCP server then answers every batch202 {"accepted":0,"disabled":true}and stores nothing. - No per-person view. A developer is only
actorHash; the session list and the API have no person column; Discord and bell messages never say who was coding. - Purpose: compliance and code quality only — the page reports what happened to the code, not who wrote it, and there is no productivity ranking.
- Short retention: 30 days by default, 7–90 configurable; events and evidence are deleted nightly.
Close-out changes (2026-10-09)
Section titled “Close-out changes (2026-10-09)”Recording-token claim codes (2026-10-09)
Section titled “Recording-token claim codes (2026-10-09)”The session token no longer passes through the model. coding_sessions enrol has no
includeToken; it returns claimCode (lgsc_…, single use) and claimExpiresIn: 300, never the
token. The plugin redeems the code itself:
python3 lr_hook.py token check --workspace 252 --mcp-url https://mcp.logrite.ai# ok | mint (no token) | mint (policy answered 401) | keep (policy unreachable: nothing)python3 lr_hook.py token claim --workspace 252 --mcp-url https://mcp.logrite.ai --code lgsc_…# stored session token …ABCD for workspace 252token checkreads only its arguments (never the repo’s.logrite/), so a new repository of the same workspace reuses the machine’s token: one token per workspace, not per repository. It answersmintonly on 401/403/404 or no token; on 0/429/5xx it sayskeep (…)and the existing token is kept.token claimcallsPOST /coding/token/claim{"claimCode":"lgsc_…"}(unauthenticated, no CORS, rate groupcoding-claim: 20 per minute per IP) → 200{token, workspaceId, last4}; an unknown, expired or used code → 404{"error":"claim_not_found"}. It refuses a token for another workspace (refused (workspace mismatch)) and writes~/.config/logrite/coding/<workspace>.tokenatomically with mode 0600. It never prints the token.lr_hook.py policy-checkwas removed.
Frameworks: enabled and enabledFeatures
Section titled “Frameworks: enabled and enabledFeatures”GET /api/v1/warden/library/frameworks rows carry enabled (a non-Off policy has a framework item
for it, or a scan profile scans for it) and enabledFeatures (the sorted features it is on for).
The plugin and /logrite:init use enabled: the installer writes framework references only for
enabled frameworks (all, with a note, when none is enabled). The skills package picks AI-coding
frameworks by "ai_coding" in enabledFeatures. Since 2026-10-15 the row no longer carries inUse.
When the effective AI-coding policies include HIPAA, the policy’s PII fields gain fullName, full_name,
birthDate, birth_date, dateOfBirth, date_of_birth, mrn.
Plugin 1.3.0
Section titled “Plugin 1.3.0”- A logging call that spans several lines is checked as one statement (up to 20 lines), so
log.info(withmember.full_name,on a later line is found; the finding points at the first line. .logrite/is skipped by every scan, including the pre-commit check.- Bash commands that edit files get a PostToolUse report in the same turn (“Logrite found N issue(s) in lines just added by that command”).
- The Stop hook asks the agent to fix each finding now without asking, and to commit (and push) the
fix when it already committed; a finding fixed in the next turn counts as
fixedInSession. - The last turn’s events are flushed by a detached child, so
claude -pexiting does not lose them.
PR events
Section titled “PR events”PR events reach the backend only when all of these are set:
| Service | Env | Value |
|---|---|---|
| webhook | LOGRITE_PR_EVENTS_ENABLED | true |
| webhook | WARDEN_INTERNAL_TOKEN | the backend’s internal token on the same estate |
| webhook | LOGRITE_BACKEND_URL | http://backend:8080 |
| backend | LOGRITE_PR_EVENTS_ENABLED | true |
| backend | LOGRITE_PR_COMMENT_ENABLED | unset / false — PR comments stay off |
The webhook forwards with X-Warden-Internal-Token (see Webhook), and the
project must have monitoring enabled. PR-event scans run non-strict: a template the project lacks is
reported in droppedTemplates instead of failing the run.
Close-out fix waves (2026-10-09)
Section titled “Close-out fix waves (2026-10-09)”Plugin 1.3.3 (final wave):
- A turn’s line counts are measured from the commit it started on, so a turn that commits its own work no longer reads 0 files changed.
- A finding the agent fixes before the turn ends is still recorded — once as found and once as
fixedInSession— so in-turn heals count on the AI Coding page. - Events without a session id (the git pre-commit check) are never sent, so no session row with an
empty id appears; the ingest drops a blank
sessionIdtoo. /logrite:initlabels the repository by the remote’s repository name, never the clone folder, and pre-approvesEdit(.logrite/**)andBash(git remote *).
Plugin 1.3.2:
- Heal carries across
--continue. The heal state file is kept past SessionEnd, so a finding raised in one run and fixed afterclaude --continuecounts as fixed in session. - Flush waits for an active flusher. When another flush is already shipping the spool, the new flush waits for it and then ships whatever remains, so no batch is left behind.
/logrite:initgives the model its non-interactive line in a fenced block marked “print this line exactly” (1.3.1); 2 of 11 runs had reworded it before.