Skip to content

fix(session): clear time.archived so archived sessions can be restored - #47848

Closed
alohaninja wants to merge 1 commit into
anomalyco:devfrom
alohaninja:fix/session-unarchive-clear-path
Closed

alohaninja wants to merge 1 commit into
anomalyco:devfrom
alohaninja:fix/session-unarchive-clear-path

Conversation

@alohaninja

@alohaninja alohaninja commented Sep 7, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #47849

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Clearing time.archived returned HTTP 200 and did nothing, so a session could be archived but never restored. Two independent defects sit on that path, and either one alone is enough to make it unreachable — which is why adding an "Unarchive" button doesn't fix it.

1. packages/core/src/session/projector.ts passed info.time.archived straight through, so clearing sent undefined. Drizzle omits undefined keys from .set(), which means "leave this column alone", not "set NULL" — the row kept its previous value. Coalescing to null emits an explicit SQL NULL.

2. The session update handler guarded on ctx.payload.time?.archived !== undefined. A client clearing the field sends {"time":{}}, because JSON.stringify drops undefined-valued properties, so the guard short-circuited and setArchived was never called. Gating on time !== undefined treats presence of the object as the intent signal and an absent archived as the clear.

toRow in session.ts carried the same shape. Not a live bug — it only reaches .values() on the import path, where an insert applies the column default — but it sits beside an onConflictDoUpdate that reuses the row, so it is coalesced for consistency.

Note ArchivedTimestamp is Schema.Finite and accepts 0 and negative values for legacy compatibility, so presence must be tested rather than truthiness.

Scope is backend only. TUI archive/unarchive follows in a separate PR so this one carries no UX decision, and the web UI is #43919.

How did you verify your code works?

Two integration tests in httpapi-session.test.ts: archive, clear via {"time":{}}, then re-read to prove the cleared value persisted rather than being masked by the response object; and a title-only PATCH leaving an existing archived timestamp alone.

Manually against a local opencode serve:

curl -X PATCH localhost:4096/session/$SID -H 'content-type: application/json' \
  -H "x-opencode-directory: $PWD" -d '{"time":{"archived":1700000000000}}'   # archived

curl -X PATCH localhost:4096/session/$SID -H 'content-type: application/json' \
  -H "x-opencode-directory: $PWD" -d '{"time":{}}'                          # cleared

curl localhost:4096/session/$SID -H "x-opencode-directory: $PWD"            # still cleared

Also confirmed a session stored with archived: 0 reads back as archived rather than active.

Fork PRs can't run the gated jobs without maintainer approval, so I ran their commands locally:

  • bun turbo typecheck --force — 30/30, 0 cached
  • packages/opencode bun run test:httpapi — 208 pass
  • httpapi-session.test.ts — 23 pass
  • packages/core — 1097 pass
  • test/cli/import.test.ts — 6 pass

Screenshots / recordings

Not a UI change — this PR is backend only.

pr47848-session-unarchive-fix

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Potential Duplicate Found:

PR #43919 - fix(session): make unarchive work and archived sessions findable

Why it's related: This PR appears to address the same core issues with session unarchiving/restoration functionality. Both PRs deal with fixing the archived timestamp handling and making the unarchive feature functional. The current PR (#47848) may be addressing the same defects that PR #43919 attempted to fix, or it could be a continuation/improvement of that work.

I recommend checking PR #43919 to see if it's still open or merged, and whether the current PR supersedes it or if they address different aspects of the same issue.

@alohaninja

Copy link
Copy Markdown
Author

Superseding — #43919 was the original 76-file attempt and stalled without review. This PR is the backend fix split out of it: the two defects in projector.ts and the update handler, plus TUI archive/unarchive. 8 files, no i18n.

#43919 will be rebased onto this and rescoped to the web UI only, or closed and reopened fresh. Nothing here depends on it.

Clearing `time.archived` returned 200 and did nothing, so a session could be
archived but never restored. Two independent defects sat on that path, either one
of which alone makes it unreachable — which is why adding an unarchive button does
not fix it.

`packages/core/src/session/projector.ts` passed `info.time.archived` straight
through, so clearing sent `undefined`. Drizzle omits undefined keys from `.set()`,
meaning "leave this column alone" rather than "set NULL", and the row kept its old
value. Coalescing to null emits an explicit SQL NULL.

The update handler guarded on `ctx.payload.time?.archived !== undefined`. A client
clearing the field sends `{"time":{}}`, because JSON.stringify drops
undefined-valued properties, so the guard short-circuited and setArchived was
never called. Gating on `time !== undefined` treats presence of the object as the
intent signal and an absent `archived` as the clear.

`toRow` in session.ts carried the same undefined-vs-null shape. Not a live bug —
it only reaches `.values()` on the import path, where an insert applies the column
default — but it sits beside an onConflictDoUpdate that reuses the row, so
coalescing keeps the two row builders consistent.

Note `ArchivedTimestamp` is `Schema.Finite` and accepts 0 and negative values for
legacy compatibility, so presence must be tested rather than truthiness.

Two integration tests cover the exact client-sends-{"time":{}} scenario and verify
the cleared value survives a re-read, plus that a title-only PATCH leaves an
existing archived timestamp alone.

Scoped to the backend fix only. TUI archive/unarchive follows separately so this
carries no UX decision. Rebased on current dev. typecheck 30/30 uncached, httpapi
208 pass, httpapi-session 23 pass, core 1097 pass, import 6 pass.
@alohaninja
alohaninja force-pushed the fix/session-unarchive-clear-path branch from 7cf4df9 to 696c05e Compare September 10, 2026 14:21
@alohaninja alohaninja changed the title fix(session): clear the archived timestamp instead of silently ignoring it fix(session): clear time.archived so archived sessions can be restored Sep 10, 2026
@github-actions github-actions Bot added needs:compliance This means the issue will auto-close after 2 hours. and removed needs:compliance This means the issue will auto-close after 2 hours. labels Sep 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

hunterchristian added a commit to chipp-ai/opencode that referenced this pull request Sep 23, 2026
…Desktop

Two independent bugs each broke unarchiving on their own: the update
handler only acted when time.archived was explicitly set (an
unarchive request arrives as {"time":{}}, so it was skipped), and the
session projector wrote undefined instead of null when archive state
was cleared (Drizzle treats undefined as "leave unchanged" on a
column, so the old archived timestamp stuck).

Ported from upstream anomalyco#6680 (54 reactions): PR anomalyco#43919
(76 files, real documented root-cause engineering) applied to this
fork almost as-is -- excluding its packages/tui/** hunk, since Desktop
doesn't use the TUI -- closed by the automated PR-cleanup bot on
2026-09-21, never reviewed. One small hunk (the same null fix in a
second file) came from the author's own backend-only split-off, PR
anomalyco#47848, still open/unmerged.

Desktop: archived sessions now appear in palette search (grouped
under "Archived"), with a new session.unarchive command and an
Archive/Unarchive toggle in the session menu. Includes the upstream
author's translated locale strings across 62 packages/app/src/i18n/*.ts
files.

Known gaps: no dedicated "Archived" section in the sidebar/home page
(archived rows are filtered out at three separate points in the data
layer before the sidebar sees them -- a real follow-up); unarchive()
is a no-op against V2-protocol servers, matching archive()'s existing
limitation; GET /session still returns archived sessions, filtered
client-side rather than server-side; not manually tested in the
running Desktop app.

Verified against this fork's current dev (not just the implementing
agent's own stale worktree base): bun typecheck clean in app, core,
opencode, desktop. New integration test in
packages/opencode/test/server/httpapi-session.test.ts fails with
either backend fix reverted. New packages/app event-reducer test
covers the session count correctly returning. Full packages/core
suite: 1164 pass (only the documented pre-existing pty flake).
packages/app: 649 pass, 10 pre-existing failures confirmed identical
with or without this change (an unrelated solid-js/Bun SSR issue).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Clearing time.archived silently fails, so archived sessions cannot be restored

1 participant