Skip to content

fix(observe): auto-create session row + surface session/start HTTP errors (closes #522)#576

Open
rohitg00 wants to merge 1 commit into
mainfrom
fix/observe-auto-session
Open

fix(observe): auto-create session row + surface session/start HTTP errors (closes #522)#576
rohitg00 wants to merge 1 commit into
mainfrom
fix/observe-auto-session

Conversation

@rohitg00
Copy link
Copy Markdown
Owner

@rohitg00 rohitg00 commented May 20, 2026

Summary

Closes #522.

Silent data-loss path: when a client posts to POST /agentmemory/observe before (or instead of) a successful POST /agentmemory/session/start, observations land in KV.observations(sessionId) but KV.sessions never gets the parent row. GET /agentmemory/sessions then returns an empty list even though the underlying observation files are on disk.

Root cause from the issue + confirmed by @huyzhui222:

  • Hook script session-start.mjs POSTs /agentmemory/session/start as fire-and-forget (fetch(...).catch(() => {})).
  • When that POST fails silently (transient server unavailability on cold-start, wrong URL, auth, …), the session record is never written.
  • observe still writes its observation, leaving orphaned rows.

Two complementary changes

1. Server-side: observe upserts the session row if missing

src/functions/observe.ts — when observe lands and kv.get(KV.sessions, sessionId) returns nothing, insert it inline using the project / cwd / timestamp / first-prompt fields already present on the payload. session/start stays the fast path (it also returns the inject-context block) but is no longer load-bearing for durability.

2. Hook-side: surface HTTP errors

src/hooks/session-start.ts — explicit process.stderr.write when session/start returns a non-2xx status, on both the fire-and-forget and inject-context paths. Network/timeout errors stay quiet (common on cold-start); HTTP errors surface so wiring problems (wrong URL, auth, etc.) become visible. The compiled plugin/scripts/session-start.mjs is regenerated by tsdown.

Diff

  • src/functions/observe.ts+22 server-side auto-upsert in the else branch where session is missing
  • src/hooks/session-start.ts+24/-3 .then(res => res.ok || log) on both paths
  • plugin/scripts/session-start.mjs+3/-1 regenerated by tsdown

Total 46 insertions, 4 deletions across 3 files.

Validation

  • npm test → 97/97 test files, 1081/1081 tests pass
  • npm run build → bundle clean

Backwards compatibility

  • session/start keeps returning the same response shape.
  • Existing session rows are untouched; only the else branch (session missing) gains behavior.
  • Hooks that previously swallowed all errors now log non-2xx — this is the desired behavior per the issue, and is consistent with the no-data-loss promise.

Summary by CodeRabbit

  • Bug Fixes

    • Sessions are now automatically created when observations are received, preventing data loss.
  • Improvements

    • Session start failures are now logged for better visibility and debugging.

Review Change Stack

…rors

Closes a silent data-loss path: when an agent hook calls
POST /agentmemory/observe before (or instead of) a successful
POST /agentmemory/session/start, observations were written to
KV.observations(sessionId) but KV.sessions never got the parent row.
GET /agentmemory/sessions then returned an empty list even though the
underlying observation files were on disk.

Two complementary changes:

1. Server-side (src/functions/observe.ts) — when observe lands and the
   session row is missing, insert it inline using the project / cwd /
   timestamp / first prompt already present on the payload. session/start
   remains the fast path (it also returns the inject-context block) but
   is no longer load-bearing for durability.

2. Hook-side (src/hooks/session-start.ts) — explicit stderr write when
   session/start returns a non-2xx status, on both the fire-and-forget
   and inject-context paths. Network / timeout errors stay quiet (common
   on cold-start); HTTP errors surface so wiring problems (wrong URL,
   auth, etc.) become visible to the user. The compiled
   plugin/scripts/session-start.mjs is regenerated by tsdown.

Tests (1081) + build pass.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agentmemory Ready Ready Preview, Comment May 20, 2026 3:47pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR fixes session creation and error visibility. When observations arrive without a corresponding session, the observe function now auto-creates the session row with metadata from the observation context. Additionally, HTTP failures from session/start requests are now logged to stderr in hook and plugin execution paths, surfacing errors that were previously silent.

Changes

Session auto-creation and error observability

Layer / File(s) Summary
Session auto-creation on observation
src/functions/observe.ts
When an observation is received for a missing session, a new session row is created with id, project, cwd, startedAt, status: "active", observationCount: 1, and optional firstPrompt derived from the normalized user prompt (truncated to 200 chars).
Session/start error logging in hooks and scripts
src/hooks/session-start.ts, plugin/scripts/session-start.mjs
HTTP failure status codes from session/start requests are logged to stderr in both the fire-and-forget telemetry path and the context-injection path. Both the hook and plugin script implementations log errors; network/timeout errors remain suppressed.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • rohitg00/agentmemory#271: Both PRs refactor the session-start hook/script's two code paths around the POST /agentmemory/session/start fetch, with #271 focusing on timeout/fire-and-forget refactoring while this PR adds stderr logging for non-OK responses.

Poem

🐰 A session was lost in the silent night,
Observations orphaned, out of sight.
Now logs will speak when things go wrong,
And sessions sprout where they belong.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: auto-creating session rows and surfacing HTTP errors, directly addressing the linked issue #522.
Linked Issues check ✅ Passed The PR fully implements both core requirements from #522: auto-creating session rows in observe when missing, and surfacing HTTP errors from session/start to stderr in both execution paths.
Out of Scope Changes check ✅ Passed All three file changes are directly scoped to addressing #522: session auto-creation logic, HTTP error logging in hooks, and the compiled script update from tsdown.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/observe-auto-session

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/functions/observe.ts (1)

45-58: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add validation for payload.project and payload.cwd.

The auto-create session logic (lines 232-233) uses payload.project and payload.cwd without validation. If these fields are missing, undefined, or non-string, the session row will be corrupted. As per coding guidelines, REST endpoints must perform input validation at system boundaries.

🛡️ Proposed fix to validate project and cwd
 if (
   !payload?.sessionId ||
   typeof payload.sessionId !== "string" ||
   !payload.hookType ||
   typeof payload.hookType !== "string" ||
   !payload.timestamp ||
-  typeof payload.timestamp !== "string"
+  typeof payload.timestamp !== "string" ||
+  (payload.project !== undefined && typeof payload.project !== "string") ||
+  (payload.cwd !== undefined && typeof payload.cwd !== "string")
 ) {
   return {
     success: false,
     error:
-      "Invalid payload: sessionId, hookType, and timestamp are required",
+      "Invalid payload: sessionId, hookType, and timestamp are required; project and cwd must be strings if present",
   };
 }
🤖 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 45 - 58, The input validation block in
observe.ts currently checks sessionId, hookType, and timestamp but misses
payload.project and payload.cwd which are later used by the auto-create session
logic (the code that creates a new session using payload.project and
payload.cwd); update the same validation to also require payload.project and
payload.cwd to be present and of type string, and adjust the returned error
message to mention project and cwd (e.g., "Invalid payload: sessionId, hookType,
timestamp, project, and cwd are required") so the endpoint rejects bad requests
before the session-creation code runs.
🧹 Nitpick comments (2)
src/functions/observe.ts (2)

206-214: ⚡ Quick win

Extract firstPrompt normalization logic to avoid duplication.

The firstPrompt normalization logic (whitespace collapse, trim, slice to 200 chars) is duplicated between the session-update path and the session-create path. Extract it to a helper function or local variable.

♻️ Proposed refactor to eliminate duplication
+// Helper: normalize user prompt for firstPrompt storage
+const normalizeFirstPrompt = (prompt: unknown): string | undefined => {
+  if (typeof prompt !== "string") return undefined;
+  const trimmed = prompt.replace(/\s+/g, " ").trim();
+  return trimmed.length > 0 ? trimmed.slice(0, 200) : undefined;
+};
+
 const session = await kv.get<{ observationCount?: number; firstPrompt?: string }>(
   KV.sessions,
   payload.sessionId,
 );
 if (session) {
   const updates: Array<{ type: "set"; path: string; value: unknown }> = [
     { type: "set", path: "updatedAt", value: new Date().toISOString() },
     {
       type: "set",
       path: "observationCount",
       value: (session.observationCount || 0) + 1,
     },
   ];
-  if (!session.firstPrompt && typeof raw.userPrompt === "string") {
-    const trimmed = raw.userPrompt.replace(/\s+/g, " ").trim();
-    if (trimmed.length > 0) {
-      updates.push({
-        type: "set",
-        path: "firstPrompt",
-        value: trimmed.slice(0, 200),
-      });
-    }
+  const normalized = normalizeFirstPrompt(raw.userPrompt);
+  if (!session.firstPrompt && normalized) {
+    updates.push({
+      type: "set",
+      path: "firstPrompt",
+      value: normalized,
+    });
   }
   await kv.update(KV.sessions, payload.sessionId, updates);
 } else {
-  const firstPrompt =
-    typeof raw.userPrompt === "string"
-      ? raw.userPrompt.replace(/\s+/g, " ").trim().slice(0, 200)
-      : undefined;
+  const firstPrompt = normalizeFirstPrompt(raw.userPrompt);
   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 } : {}),
   });
 }

Also applies to: 226-229

🤖 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 206 - 214, The whitespace
collapse/trim/length-check/slice(0,200) logic for creating firstPrompt is
duplicated; extract it into a small helper (e.g., normalizePrompt or
normalizeUserPrompt) and call it from both places where you set
session.firstPrompt (the session-update branch checking session.firstPrompt and
raw.userPrompt and the session-create branch that also handles raw.userPrompt),
returning either a normalized string or null/empty if nothing remains; then
replace the inline logic with a call to the helper and push the existing update
object only when the helper returns a non-empty value.

218-225: 💤 Low value

Simplify comment to focus on intent rather than implementation.

The comment explains implementation details and history that are better captured in the PR description or commit message. As per coding guidelines, avoid comments explaining WHAT — use clear naming instead. Focus on the essential rationale.

📝 Suggested simplification
-// 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).
+// Auto-create session if observe arrives before session/start succeeds.
🤖 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 218 - 225, Replace the long
explanatory comment that begins with "// Auto-create the session row when an
observation arrives..." with a concise intent-focused line stating why the code
creates a session row (e.g., "Ensure a session row exists when observations
arrive without a prior session/start to prevent orphaned observations"). Remove
historical/implementation details and references to transient server behavior;
keep only the essential rationale for the creation behavior so the code intent
is clear.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@src/functions/observe.ts`:
- Around line 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.

---

Outside diff comments:
In `@src/functions/observe.ts`:
- Around line 45-58: The input validation block in observe.ts currently checks
sessionId, hookType, and timestamp but misses payload.project and payload.cwd
which are later used by the auto-create session logic (the code that creates a
new session using payload.project and payload.cwd); update the same validation
to also require payload.project and payload.cwd to be present and of type
string, and adjust the returned error message to mention project and cwd (e.g.,
"Invalid payload: sessionId, hookType, timestamp, project, and cwd are
required") so the endpoint rejects bad requests before the session-creation code
runs.

---

Nitpick comments:
In `@src/functions/observe.ts`:
- Around line 206-214: The whitespace collapse/trim/length-check/slice(0,200)
logic for creating firstPrompt is duplicated; extract it into a small helper
(e.g., normalizePrompt or normalizeUserPrompt) and call it from both places
where you set session.firstPrompt (the session-update branch checking
session.firstPrompt and raw.userPrompt and the session-create branch that also
handles raw.userPrompt), returning either a normalized string or null/empty if
nothing remains; then replace the inline logic with a call to the helper and
push the existing update object only when the helper returns a non-empty value.
- Around line 218-225: Replace the long explanatory comment that begins with "//
Auto-create the session row when an observation arrives..." with a concise
intent-focused line stating why the code creates a session row (e.g., "Ensure a
session row exists when observations arrive without a prior session/start to
prevent orphaned observations"). Remove historical/implementation details and
references to transient server behavior; keep only the essential rationale for
the creation behavior so the code intent is clear.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5d3b23a5-0d27-4108-ac5e-a93616898a1f

📥 Commits

Reviewing files that changed from the base of the PR and between edd1ceb and ac53842.

📒 Files selected for processing (3)
  • plugin/scripts/session-start.mjs
  • src/functions/observe.ts
  • src/hooks/session-start.ts

Comment thread src/functions/observe.ts
Comment on lines +230 to +238
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 } : {}),
});
Copy link
Copy Markdown

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Sessions never created and silent data loss

1 participant