Compare commits

...
Author SHA1 Message Date
taodeng b169952039 fix(cli): set DISABLE_AUTOUPDATER=1 in manual restart fallback
When ocp restart falls back to nohup (no launchctl/systemd
service registered), prepend DISABLE_AUTOUPDATER=1 so the
spawned server.mjs and any claude subprocess it spawns skip
the in-binary auto-updater check.

Why: claude-code's native binary contains an auto-updater
that fires after each successful invocation. Partial
install.cjs failures during update leave bin/claude.exe as
an ASCII shim → spawnSync ENOEXEC → OCP slot lockup
(symptoms in #37, #40 forensics). Setting this env at the
launch site stops the trigger.

Note: only covers the manual nohup path. Users with
launchctl plist or systemd unit must add the env there too
(plist EnvironmentVariables or systemd Environment=).
Authoritative settings location is ~/.claude/settings.json
env key — see learnings/claude_code_disable_autoupdate.md
in cc-rules for the full 4-layer guidance.

Verified: 24h+ stable on Mac mini after applying full
multi-layer fix on 2026-04-30 04:20 (claude.exe mtime
unchanged, OCP uptime 22h54m, 0 errors over 3 real calls).

No server.mjs change — ALIGNMENT.md unaffected.
2026-05-01 03:24:21 +10:00
733a2ed4c2 chore(server): add session-create-vs-resume + lifetime instrumentation (refs #41 #42) (#51)
Adds structured logging for the two forensic candidates so the eventual
fixes can be evidence-driven (per #41/#42 bodies: "Evidence needed before
code change — do not patch speculatively"). NO bug fix in this PR — the
stale-create-entry behavior (#41) and the lastUsed-resistant-expiry
behavior (#42) are intentionally left unchanged so they can be observed
in production /logs.

Three changes (server.mjs):

1. Session-create branch records `firstSeen` alongside `lastUsed` (same
   timestamp), giving the sweep loop an absolute-age signal independent
   of the actively-bumped `lastUsed` field. Resume branch is untouched
   so `firstSeen` is preserved across resumes.

2. handleSessionFailure now emits a structured `session_failure` event
   with `mode: "resume"` (action: "deleted") OR `mode: "create"`
   (action: "kept"). The "kept" branch makes #41's "stale create entries
   never deleted" pattern visible in /logs without changing behavior.

3. TTL sweep emits `session_expired` (info, with idleMs + ageMs) on
   actual expiry, and `session_long_lived` (warn) when ageMs > 4×TTL
   but the entry is not being expired this tick. The latter is the
   #42 evidence trigger — a session whose lastUsed keeps getting bumped
   and so resists idle-based expiry.

cli.js citation: N/A — logEvent payloads are OCP-internal observability,
never sent on the wire to the Anthropic API. ALIGNMENT.md Rule 2 (no
invention) is scoped to endpoints/headers/request/response fields, not
to internal server logging.

Independent reviewer: fresh-context sonnet APPROVE_WITH_MINOR.
Two minor notes — repeated `session_long_lived` emissions per 60s
sweep tick is expected (downstream analysis dedupes by conversationId).
LOC: +19 / -3 = +16 net.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 19:19:38 +10:00
dtzp555-maxandGitHub ca2d23d230 chore(speckit): add compatibility/metadata frontmatter to SKILL.md files (refs #39) (#50)
Backfill the two YAML frontmatter blocks that upstream
`specify_cli/agents.py:build_skill_frontmatter()` emits:

  compatibility: "Requires spec-kit project structure with .specify/ directory"
  metadata:
    author: github-spec-kit
    source: claude:templates/commands/<cmd>.md

Applied uniformly across all 9 speckit-* SKILL.md files. Each file gets the
two blocks appended to its existing frontmatter (between the closing fields
and the closing `---`). No functional change — these fields are informational
only, per #39 body and PR #38 reviewer note.

Verified:
- All 9 files have `compatibility:` key (grep -lc, 9/9)
- All 9 files have `metadata:` block with `author: github-spec-kit` (9/9)
- All 9 files have `source: claude:templates/commands/<cmd>.md` matching the
  command name (9/9)
- No existing fields modified or removed; pure additive +4 lines per file

Upstream reference (build_skill_frontmatter):
https://github.com/github/spec-kit/blob/main/src/specify_cli/agents.py
2026-04-25 19:19:35 +10:00
11 changed files with 56 additions and 4 deletions
+4
View File
@@ -4,6 +4,10 @@ description: Perform a non-destructive cross-artifact consistency and quality an
argument-hint: "Optional focus areas for analysis" argument-hint: "Optional focus areas for analysis"
user-invocable: true user-invocable: true
disable-model-invocation: false disable-model-invocation: false
compatibility: "Requires spec-kit project structure with .specify/ directory"
metadata:
author: github-spec-kit
source: claude:templates/commands/analyze.md
--- ---
## User Input ## User Input
@@ -4,6 +4,10 @@ description: Generate a custom checklist for the current feature based on user r
argument-hint: "Domain or focus area for the checklist" argument-hint: "Domain or focus area for the checklist"
user-invocable: true user-invocable: true
disable-model-invocation: false disable-model-invocation: false
compatibility: "Requires spec-kit project structure with .specify/ directory"
metadata:
author: github-spec-kit
source: claude:templates/commands/checklist.md
--- ---
## Checklist Purpose: "Unit Tests for English" ## Checklist Purpose: "Unit Tests for English"
+4
View File
@@ -4,6 +4,10 @@ description: Identify underspecified areas in the current feature spec by asking
argument-hint: "Optional areas to clarify in the spec" argument-hint: "Optional areas to clarify in the spec"
user-invocable: true user-invocable: true
disable-model-invocation: false disable-model-invocation: false
compatibility: "Requires spec-kit project structure with .specify/ directory"
metadata:
author: github-spec-kit
source: claude:templates/commands/clarify.md
--- ---
## User Input ## User Input
@@ -4,6 +4,10 @@ description: Create or update the project constitution from interactive or provi
argument-hint: "Principles or values for the project constitution" argument-hint: "Principles or values for the project constitution"
user-invocable: true user-invocable: true
disable-model-invocation: false disable-model-invocation: false
compatibility: "Requires spec-kit project structure with .specify/ directory"
metadata:
author: github-spec-kit
source: claude:templates/commands/constitution.md
--- ---
## User Input ## User Input
@@ -4,6 +4,10 @@ description: Execute the implementation plan by processing and executing all tas
argument-hint: "Optional implementation guidance or task filter" argument-hint: "Optional implementation guidance or task filter"
user-invocable: true user-invocable: true
disable-model-invocation: false disable-model-invocation: false
compatibility: "Requires spec-kit project structure with .specify/ directory"
metadata:
author: github-spec-kit
source: claude:templates/commands/implement.md
--- ---
## User Input ## User Input
+4
View File
@@ -4,6 +4,10 @@ description: Execute the implementation planning workflow using the plan templat
argument-hint: "Optional guidance for the planning phase" argument-hint: "Optional guidance for the planning phase"
user-invocable: true user-invocable: true
disable-model-invocation: false disable-model-invocation: false
compatibility: "Requires spec-kit project structure with .specify/ directory"
metadata:
author: github-spec-kit
source: claude:templates/commands/plan.md
--- ---
## User Input ## User Input
+4
View File
@@ -4,6 +4,10 @@ description: Create or update the feature specification from a natural language
argument-hint: "Describe the feature you want to specify" argument-hint: "Describe the feature you want to specify"
user-invocable: true user-invocable: true
disable-model-invocation: false disable-model-invocation: false
compatibility: "Requires spec-kit project structure with .specify/ directory"
metadata:
author: github-spec-kit
source: claude:templates/commands/specify.md
--- ---
## User Input ## User Input
+4
View File
@@ -4,6 +4,10 @@ description: Generate an actionable, dependency-ordered tasks.md for the feature
argument-hint: "Optional task generation constraints" argument-hint: "Optional task generation constraints"
user-invocable: true user-invocable: true
disable-model-invocation: false disable-model-invocation: false
compatibility: "Requires spec-kit project structure with .specify/ directory"
metadata:
author: github-spec-kit
source: claude:templates/commands/tasks.md
--- ---
## User Input ## User Input
@@ -4,6 +4,10 @@ description: Convert existing tasks into actionable, dependency-ordered GitHub i
argument-hint: "Optional filter or label for GitHub issues" argument-hint: "Optional filter or label for GitHub issues"
user-invocable: true user-invocable: true
disable-model-invocation: false disable-model-invocation: false
compatibility: "Requires spec-kit project structure with .specify/ directory"
metadata:
author: github-spec-kit
source: claude:templates/commands/taskstoissues.md
--- ---
## User Input ## User Input
+1 -1
View File
@@ -604,7 +604,7 @@ cmd_restart() {
self_r="${BASH_SOURCE[0]}" self_r="${BASH_SOURCE[0]}"
while [[ -L "$self_r" ]]; do self_r="$(readlink "$self_r")"; done while [[ -L "$self_r" ]]; do self_r="$(readlink "$self_r")"; done
script_dir="$(cd "$(dirname "$self_r")" && pwd)" script_dir="$(cd "$(dirname "$self_r")" && pwd)"
nohup node "$script_dir/server.mjs" >> "$HOME/.ocp/logs/proxy.log" 2>&1 & DISABLE_AUTOUPDATER=1 nohup node "$script_dir/server.mjs" >> "$HOME/.ocp/logs/proxy.log" 2>&1 &
fi fi
sleep 3 sleep 3
if curl -sf --max-time 5 "$PROXY/health" > /dev/null 2>&1; then if curl -sf --max-time 5 "$PROXY/health" > /dev/null 2>&1; then
+19 -3
View File
@@ -165,9 +165,18 @@ const sessions = new Map(); // conversationId → { uuid, messageCount, lastUsed
const sessionCleanupInterval = setInterval(() => { const sessionCleanupInterval = setInterval(() => {
const now = Date.now(); const now = Date.now();
for (const [id, s] of sessions) { for (const [id, s] of sessions) {
if (now - s.lastUsed > SESSION_TTL) { const idleMs = now - s.lastUsed;
const ageMs = s.firstSeen ? now - s.firstSeen : null;
if (idleMs > SESSION_TTL) {
sessions.delete(id); sessions.delete(id);
console.log(`[session] expired ${id.slice(0, 12)}... (idle ${Math.round((now - s.lastUsed) / 60000)}m)`); console.log(`[session] expired ${id.slice(0, 12)}... (idle ${Math.round(idleMs / 60000)}m)`);
logEvent("info", "session_expired", { conversationId: id.slice(0, 12) + "...", idleMs, ageMs });
} else if (ageMs !== null && ageMs > 4 * SESSION_TTL) {
// #42 evidence-gathering: a session whose firstSeen is more than 4× TTL old
// but whose lastUsed keeps getting bumped (never idle long enough to expire)
// is the suspected bug. Log without action so the pattern can be confirmed
// in /logs. Do NOT enforce an absolute age cap here speculatively.
logEvent("warn", "session_long_lived", { conversationId: id.slice(0, 12) + "...", idleMs, ageMs });
} }
} }
}, 60000); }, 60000);
@@ -406,7 +415,8 @@ function spawnClaudeProcess(model, messages, conversationId) {
} else if (conversationId) { } else if (conversationId) {
const uuid = randomUUID(); const uuid = randomUUID();
sessions.set(conversationId, { uuid, messageCount: messages.length, lastUsed: Date.now(), model: cliModel }); const now = Date.now();
sessions.set(conversationId, { uuid, messageCount: messages.length, firstSeen: now, lastUsed: now, model: cliModel });
sessionInfo = { uuid, resume: false }; sessionInfo = { uuid, resume: false };
stats.sessionMisses++; stats.sessionMisses++;
prompt = messagesToPrompt(messages); prompt = messagesToPrompt(messages);
@@ -458,7 +468,13 @@ function spawnClaudeProcess(model, messages, conversationId) {
function handleSessionFailure() { function handleSessionFailure() {
if (sessionInfo?.resume && conversationId) { if (sessionInfo?.resume && conversationId) {
console.warn(`[session] resume failed for ${conversationId.slice(0, 12)}..., removing stale session`); console.warn(`[session] resume failed for ${conversationId.slice(0, 12)}..., removing stale session`);
logEvent("warn", "session_failure", { mode: "resume", conversationId: conversationId.slice(0, 12) + "...", action: "deleted" });
sessions.delete(conversationId); sessions.delete(conversationId);
} else if (sessionInfo && !sessionInfo.resume && conversationId) {
// #41 evidence-gathering: session-create failures currently leave a stale entry
// in the sessions map. Log without action so the staleness pattern can be
// confirmed in /logs before any code change. Do NOT delete here speculatively.
logEvent("warn", "session_failure", { mode: "create", conversationId: conversationId.slice(0, 12) + "...", action: "kept" });
} }
} }