Skip to content

AI Coding

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.

PieceWhere it runsWhat it does
Plugin hooks (plugin/hooks/hooks.json → lr.sh → lr_hook.py)Developer machine, python3 3.8+, stdlib onlyScan added lines, fix round at Stop, spool and ship events
POST /coding/events, GET /coding/policyMCP server (https://mcp.logrite.ai)Token-authenticated ingest and policy — see MCP Server → Session-ingest routes
coding-events- per-workspace index, coding_issue tablesLogrite backendEvents, issue roll-up, retention
POST /coding/describeAnalyzerThe AI write-up of each new issue
/ai-coding page, Settings → AI codingConsoleTriage; workspace switches

Setup on each developer machine is two steps: install the plugin, then run /logrite:init in each repository.

Terminal window
curl -fsSL https://mcp.logrite.ai/install.sh | sh

The installer has three modes:

ModeHowWhat it registers
Key from the environmentcurl -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 promptno variable set: it asks for the key on the terminal with echo offsame as above
OAuthpress Enter at the prompt, or run with no terminal and no variableclaude 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.

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).

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.

eventTypeSent whenExtra fields
session_startSessionStart, recording on—
turnStop, once the turn is finaltools (counts by tool name), toolErrors, tokens (input, output, cacheRead), filesChanged, linesAdded, linesRemoved, durationMs
findingStop (once per finding, after any fix round)finding — see below
healStop after a fix roundheal (round, findings, resolved)
commit_scanWarn-only pre-commitcommit.findings (a count); client.name is git-pre-commit
session_endSessionEnddurationMs

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.

Levelfinding carries
metadatafingerprint, ruleId, source, severity, framework, line, ext, fixedInSession, and pathHash = sha256(path). No message, snippet or path — the server drops them whatever the plugin sent
findingsAll of the above plus message (≤300 chars), path (≤300) and a masked snippet (≤200)
extendedToday the same as findings. Neither the plugin (lr_common.shape_finding) nor the MCP server (coding_ingest._finding) treats it differently

The plugin sends events with a write-only lgsi_live_ token, bound to one workspace.

  • Minted by /logrite:init, through the MCP tool coding_sessions with action enrol and include_token: true (without that flag the token is withheld from the tool result).
  • Stored outside the repository at ~/.config/logrite/coding/<workspaceId>.token, mode 0600. The environment variable LOGRITE_CODING_TOKEN overrides the file. It is never written to .logrite/config.json, never into the repository, never echoed.
  • Accepted only by POST /coding/events and GET /coding/policy on the MCP server. It is not an API key: /sse and every REST route refuse it, and those two routes refuse an lgp_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)WhoResult
POST /tokens body {label}Any workspace member (login, API key, or the MCP connection){id, token, last4, label, createdAt} — the plaintext once
GET /tokensAny member{tokens:[{id, label, last4, createdBy, createdAt, lastUsedAt, revokedAt}]} — never the token or its hash
DELETE /tokens/{id}Workspace admin, or whoever minted it204; 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.

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).

HookTimeoutWhat it does
SessionStart5 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
UserPromptSubmit5 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|MultiEdit10 sScans 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
Stop30 s (+ async flush)Records the turn; may run a fix round (below)
SessionEnd2 sSpool 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.

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.heal is on (default on);
  • the turn has had fewer than coding.healRounds rounds (0–2, default 1);
  • Claude Code’s stop_hook_active is 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 (default medium) 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=***).

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.

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.

Installed only when the developer says yes at /logrite:init (lr_hook.py precommit install):

  • The hook file is $(git config core.hooksPath)/pre-commit when that is set, else .git/hooks/pre-commit. The check itself (logrite_precommit.py and its scanner copy logrite_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.
Terminal window
# >>> 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.

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.

sourceSwitchRules
compliancecoding.scan.complianceThe 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
loggingcoding.scan.loggingThe three logging standards below
businesscoding.scan.businessEnabled 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):

ruleIdSeverityMessagePresent when
logging.secret-in-loghighA log statement may carry a credentialA log call passes a credential value (see below)
logging.pii-in-logmediumA log statement names a person rather than an idlogging.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-fieldlowA log statement omits the required correlation fieldlogging.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.

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. A fixedInSession sighting 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 until mutedUntil), FIXED.
    • A FIXED issue seen again after it was marked fixed reopens as OPEN with regressed: true — shown as “Came back”.
    • A mute that has expired drops out of the policy’s muted list at once; the issue reopens the next time it is seen.
    • A person setting the status clears regressed.
  • Triage (PATCH) needs the coding-triage role floor — Maintainer by default — because muting an issue silences it in every developer’s fix round.

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.

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.

ChannelGateContent
Discord, category “AI coding sessions” (coding)Connection on and the category tickedTitle (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).

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).

RouteReturns
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).

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.

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.

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 level metadata: “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:

ButtonDoes
Copy issue detailsMarkdown: title, rule, type, severity, framework, counts, description, affected path:line, proposed fix — never snippets
Create ruleOpens 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 CodeCopies /logrite:fix <id> — toast “Copied — paste it into Claude Code”
Open pull requestCortex 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”

Columns Started, Repo, Turns, Files changed, Findings, Fixed in-session — deliberately no person column.

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.”

Each step is idempotent: running it again changes nothing.

  1. Calls get_workspace_context; if it fails, stops and says to re-run the installer or run /mcp → Authenticate.
  2. Finds the remote (origin, else the only remote) and uses the directory name as the label.
  3. Calls enrol_repository with remoteUrl. 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:init again.
  4. Uses enabledFrameworks from step 1 (frameworks enabled for AI coding, K1); only when that is empty does it ask one multi-select of list_compliance_frameworks, SOC2 preselected. addons from workspace_addons list.
  5. 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.
  6. 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 runs lr_hook.py token check --workspace <id> --mcp-url <url>; on mint … it calls coding_sessions enrol and stores the token with lr_hook.py token claim (see Recording-token claim codes). The model never sees the token.
  7. 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.”

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.

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.

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.

  • Metadata and masked findings by default (coding.capture = findings); metadata removes messages, paths and snippets. Never prompts, replies or code.
  • Visible: every recorded session opens with the notice above, and /logrite:privacy shows exactly what is recorded on that machine.
  • Opt out per repository with "recordSessions": false in .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 batch 202 {"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.

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:

Terminal window
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 252
  • token check reads 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 answers mint only on 401/403/404 or no token; on 0/429/5xx it says keep (…) and the existing token is kept.
  • token claim calls POST /coding/token/claim {"claimCode":"lgsc_…"} (unauthenticated, no CORS, rate group coding-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>.token atomically with mode 0600. It never prints the token.
  • lr_hook.py policy-check was removed.

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.

  • A logging call that spans several lines is checked as one statement (up to 20 lines), so log.info( with member.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 -p exiting does not lose them.

PR events reach the backend only when all of these are set:

ServiceEnvValue
webhookLOGRITE_PR_EVENTS_ENABLEDtrue
webhookWARDEN_INTERNAL_TOKENthe backend’s internal token on the same estate
webhookLOGRITE_BACKEND_URLhttp://backend:8080
backendLOGRITE_PR_EVENTS_ENABLEDtrue
backendLOGRITE_PR_COMMENT_ENABLEDunset / 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.

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 sessionId too.
  • /logrite:init labels the repository by the remote’s repository name, never the clone folder, and pre-approves Edit(.logrite/**) and Bash(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 after claude --continue counts 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:init gives 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.