Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion plugin/scripts/session-start.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ async function main() {
fetch(url, {
...init,
signal: AbortSignal.timeout(REGISTER_TIMEOUT_MS)
}).then((res) => {
if (!res.ok) process.stderr.write(`[agentmemory] session/start returned ${res.status} for ${sessionId}\n`);
}).catch(() => {});
return;
}
Expand All @@ -52,7 +54,7 @@ async function main() {
if (res.ok) {
const result = await res.json();
if (result.context) process.stdout.write(result.context);
}
} else process.stderr.write(`[agentmemory] session/start returned ${res.status} for ${sessionId}\n`);
} catch {}
}
main();
Expand Down
22 changes: 22 additions & 0 deletions src/functions/observe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,28 @@ export function registerObserveFunction(
}
}
await kv.update(KV.sessions, payload.sessionId, updates);
} else {
// Auto-create the session row when an observation arrives before
// (or instead of) a successful session/start call. The hook
// scripts call session/start as fire-and-forget; on transient
// server unavailability that POST is silently dropped, leaving
// observations orphaned from GET /agentmemory/sessions even
// though they're present on disk. Re-creating here closes the
// data-loss hole permanently; session/start remains the fast
// path (it also returns the injected context).
const firstPrompt =
typeof raw.userPrompt === "string"
? raw.userPrompt.replace(/\s+/g, " ").trim().slice(0, 200)
: undefined;
await kv.set(KV.sessions, payload.sessionId, {
id: payload.sessionId,
project: payload.project,
cwd: payload.cwd,
startedAt: payload.timestamp,
status: "active",
observationCount: 1,
...(firstPrompt ? { firstPrompt } : {}),
});
Comment on lines +230 to +238
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include updatedAt field when auto-creating sessions.

The existing session update path sets updatedAt (line 199), but the new auto-create path omits it. This creates inconsistent session records where some have updatedAt and others don't, complicating queries and violating the principle of uniform schema.

🔧 Proposed fix to add updatedAt
+const now = new Date().toISOString();
 await kv.set(KV.sessions, payload.sessionId, {
   id: payload.sessionId,
   project: payload.project,
   cwd: payload.cwd,
   startedAt: payload.timestamp,
+  updatedAt: now,
   status: "active",
   observationCount: 1,
   ...(firstPrompt ? { firstPrompt } : {}),
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await kv.set(KV.sessions, payload.sessionId, {
id: payload.sessionId,
project: payload.project,
cwd: payload.cwd,
startedAt: payload.timestamp,
status: "active",
observationCount: 1,
...(firstPrompt ? { firstPrompt } : {}),
});
const now = new Date().toISOString();
await kv.set(KV.sessions, payload.sessionId, {
id: payload.sessionId,
project: payload.project,
cwd: payload.cwd,
startedAt: payload.timestamp,
updatedAt: now,
status: "active",
observationCount: 1,
...(firstPrompt ? { firstPrompt } : {}),
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/functions/observe.ts` around lines 230 - 238, The auto-create branch that
calls kv.set for KV.sessions (key payload.sessionId) omits updatedAt, causing
inconsistent session records; update the object passed to kv.set in observe.ts
(the auto-create block that sets id, project, cwd, startedAt, status,
observationCount, and optionally firstPrompt) to include updatedAt (use
payload.timestamp or the same timestamp used for startedAt) so newly-created
sessions have the same schema as the update path.

}

// Per-observation LLM compression is opt-in as of 0.8.8 (#138).
Expand Down
24 changes: 21 additions & 3 deletions src/hooks/session-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,24 @@ async function main() {
if (!INJECT_CONTEXT) {
// Pure telemetry path: caller never reads the response, so don't
// block on it. AbortSignal.timeout caps the wait the event loop
// gives the pending socket before exit.
// gives the pending socket before exit. Server-side observe now
// auto-creates a session row when one is missing (#522), so a
// dropped POST here is a missed context-inject, not data loss.
fetch(url, {
...init,
signal: AbortSignal.timeout(REGISTER_TIMEOUT_MS),
}).catch(() => {});
})
.then((res) => {
// Surface HTTP errors so wiring problems (wrong URL, auth)
// don't stay invisible. Network/timeout errors fall through
// to .catch() and stay quiet — those are common on cold-start.
if (!res.ok) {
process.stderr.write(
`[agentmemory] session/start returned ${res.status} for ${sessionId}\n`,
);
}
})
.catch(() => {});
return;
}

Expand All @@ -80,9 +93,14 @@ async function main() {
if (result.context) {
process.stdout.write(result.context);
}
} else {
process.stderr.write(
`[agentmemory] session/start returned ${res.status} for ${sessionId}\n`,
);
}
} catch {
// silently fail -- don't block Claude Code startup
// network/timeout — don't block Claude Code startup; observe-side
// will still pick up the session via auto-upsert (#522).
}
}

Expand Down