Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[feat] Session archive: no local paths, and record what the session ran as

Open
#1,579 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
35/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Active
Tech stack
git, node.js, typescript
Domain
devtools, tooling

Research direction

Start with SyncManager.saveSessionLocked in sync.ts and the existing tests under src/__tests__/; then trace the named publication paths and scrubSession in scrub.ts. First verify the Claude Code thinking-signature behavior described in Slice 2. Done means the acceptance criteria are covered by tests, the real-session resume check is recorded, docs are updated, and lint, typecheck, and tests pass.

Written by the indexing model from the issue text.

Description

enhancement

Goal

A session pushed to the team archive (teamai session push --scrub, teamai-share-session, workflow publish --sessions, score --drain) should keep no absolute local paths of the member's machine, and should record what the session ran as, so the team can debug it, group it by team revision, and compare runs.

Ships as one PR, built and verified slice by slice.

Today

A real _index.json entry after teamai session push --scrub:

{
  "sessionName": "claude-code_test_20261030",
  "author": "Alice Doe",
  "platform": "claude-code",
  "cwd": "/Users/alice/Repositorios/app",
  "repoIdentity": "github.com/acme/app",
  "sessionId": "6e6403a5-6ada-4c5a-95a3-1f302fe92912"
}

The same path is in meta.json (origin.cwd) and throughout the scrubbed transcript:

tool_call   {"command": "cat /Users/alice/Repositorios/app/skill-data/workflow/references/hosts.md"}
tool_result Output is being written to: /private/tmp/claude-502/-Users-alice-Repositorios-app/6e64…/tasks/bl7y.output

Nothing records the team revision the session ran with. docs/guide/scorers.md:225 states it: a pushed session's label "has no agent, because what a session ran as is recorded only on the machine that ran it". The data exists locally: the session-start hook captures it (SessionCaptures, #1178, built from #1176's AgentObservation), but push never carries it.

Where the archived cwd is read

Reader Uses archived cwd?
Workflow restore, openSessionArchive (archive.ts:58,87) No: looks up by repoIdentity(target.cwd) + platform + sessionId, then overwrites cwd with target.cwd
teamai session resume (session-cmd.ts:549-561) No: overwrites with --cwd or process.cwd()
teamai session pull No: uses the local cwd for repoIdentity only
score --team-session (scoring.ts:306) Log line only
Dashboard session sharing, scorer name join No

So the absolute path is not needed to resume or hand over a session.


Slice 1: archived cwd is relative to its repo or workspace

The cwd is stored relative to the root the session belongs to. That keeps the sub-directory for monorepos and multi-repo workspaces and drops the machine prefix.

Native session.cwd Archived cwd
/Users/alice/app (git top level) .
/Users/alice/app/packages/api (monorepo) packages/api
/Users/alice/ws/svc-a (child repo of a workspace) . (root is svc-a, its own repo)
/Users/alice/ws/backend (plain folder inside a non-git teamai workspace ws/) backend (root is the workspace's projectRoot)
/Users/alice/tmp/scratch (no repo, no teamai config) ""
md5:3f2a… (codebuddy-ide placeholder) ""
Removed workflow worktree ""

Rules:

  • Root = git top level of cwd, else the projectRoot of the teamai config found for cwd, else none ("").
  • Computed once in SyncManager.saveSessionLocked (sync.ts:704). All four push paths go through it: archive.ts:159, archive.ts:192, session-cmd.ts:273, session-migrate-cmd.ts:482.
  • The index entry takes cwd from meta.origin.cwd, not session.cwd (sync.ts:749). Today a save writes session.cwd and a rebuild (sync.ts:1026) writes meta.origin.cwd: they must agree.
  • authorForCwd and deriveArchiveIdentity keep reading the native cwd before saving. The archive key does not change.
  • loadSession reads origin.cwd ?? "". Restore and resume keep overwriting it.

After:

{ "sessionName": "claude-code_test_20261030", "cwd": ".", "repoIdentity": "github.com/acme/app", "...": "..." }

Slice 2: a scrubbed thinking block still resumes (verify first)

scrubBlock (scrub.ts:58-63) masks a thinking block's text and keeps its signature. The Claude Code adapter writes it back (claude-code.ts:260), so a resumed session sends a signature that no longer matches its text. This already happens today whenever a secret is masked inside thinking, and Slice 3 makes it common, since thinking often names paths. Whether the API rejects it is not known: thinking from earlier turns is dropped from context, so its signature may never be checked.

Start with the check, before any code in this slice:

  1. Resume a Claude Code session whose thinking in an earlier turn has altered text and its original signature. → verify: the first new turn is accepted.
  2. Accepted: this slice ships no code. Masked thinking stays a native, signed block, and the PR records the check.
  3. Rejected: changed blocks only are archived as text, as below.
Thinking block Archived as (plan B only)
Unchanged by the scrub { type: "thinking", text, signature }, as today
Changed by the scrub { type: "text", text: "<thinking>\n…masked…\n</thinking>" }, no signature (the degradeThinkingBlocks convention, migrate.ts:144)

Plan B keeps every word of the reasoning for the team, but a resumed session carries those blocks as text, which the API does not drop from context. Do not leave an unsigned native thinking block: it is not known to be valid either.

Slice 3: published transcripts carry no local paths

On archive publication only, the scrub rewrites local paths in the title, text, thinking text, tool call argument string values, tool result text, and string metadata kept in the message.

Before After
/Users/alice/app/src/sync.ts <repo>/src/sync.ts
/Users/alice/app <repo>
/Users/alice/.claude/settings.json ~/.claude/settings.json
/Users/alice/other-project/x.ts ~/other-project/x.ts
/private/tmp/claude-502/-Users-alice-app/6e64…/tasks/b.output <tmp>/<project>/6e64…/tasks/b.output
C:\Users\alice\app\src\sync.ts <repo>\src\sync.ts
file:///Users/alice/app/README.md file://<repo>/README.md
src/sync.ts, /api/v1/users, https://github.com/acme/app unchanged

Rules:

  • <repo> is the Slice 1 root, ~ this machine's home directory, <tmp> the per-user temp root (os.tmpdir() resolved, /private/tmp/claude-<uid>, /tmp/claude-<uid>). <project> is the tool's encoded form of the root (encodeCwdClaude). Longest prefix wins.
  • Walk structured values, never a regex over serialized JSON: a Windows path is escaped there.
  • Publication only. AgentSession.find also scrubs for scoring and skill attribution, which match tool call paths against delivered file paths (session-work.ts:128, skill-use.ts:300). That read keeps native paths. The archive push paths pass an explicit option.
  • Tool call change detection compares values (next === raw), not the count of <REDACTED: markers (scrub.ts:70). Otherwise a path rewrite with no secret in the same arguments is dropped.
  • Image filePath handling is unchanged.
  • Push without --scrub keeps its current as-is behaviour and warning.

Slice 4: the archive records what the session ran as

At push, read the session's local captures and store their settled observation in the meta:

 SessionSyncMeta {
   origin: { platform, author, cwd, repoIdentity, createdAt, sessionId, title? }
+  agent?: AgentObservation   // SessionCaptures.agentOf(captures, reportedModel)
 }
 IndexEntry {
+  teamRevision?: string      // agent.team.revision
 }

Example meta.json:

"agent": {
  "v": 1,
  "tool": { "name": "claude", "version": "2.1.301" },
  "model": { "requested": "claude-opus-5-5", "reported": "claude-opus-5-5" },
  "team": { "revision": "7a6134a25e74…", "localChanges": false, "selection": { "primaryRole": "backend" } },
  "project": { "commit": "8e04ff430…", "dirty": true },
  "teamai": "0.31.0",
  "driver": "session",
  "fingerprint": "…"
}
Case agent Index teamRevision
One capture the observation team.revision
Captures agree the observation team.revision
A capture is marked teamChanged without team and fingerprint (agentOf rule) absent
Captures differ, or none (session predates the hook, tool has no hook, captures swept) absent absent
Session teamai launched (workflow step) absent: none is captured (captures.ts:61); the run snapshot already records each pass's agent absent
session migrate --push absent absent

Rules:

  • Never fill it from the team clone's HEAD at push time. That is the revision at push, not the one the session ran with.
  • Parse with AgentObservationSchema on read: an invalid value is treated as absent. The schema is an allowlist with no path fields.
  • score --team-session uses the archived agent as the label's agent instead of none (scoring.ts:310), so pushed sessions group by tool, model and team revision on the Scorers page.
  • Re-push of the same session overwrites agent with what the captures say at that time.

What this enables: show the exact skill text a session had (git show <rev>:skills/<name>/SKILL.md), flag sessions that ran on a stale team revision or with localChanges, group sessions and scores by team revision, and compare runs with the same project.commit and different team.revision.


Acceptance criteria

  1. Pushing a Claude session started in /…/app/packages/api writes "cwd": "packages/api" to both meta.json and _index.json. Rebuilding the index produces the same entry.
  2. Each row of the Slice 1 table holds, for session push, session migrate --push, workflow publish --sessions and score --drain.
  3. A session archived with a relative cwd restores through a workflow handover and through teamai session resume on another directory, and its turns are intact.
  4. A Claude Code session whose thinking was masked by the scrub (a secret or a local path) resumes after restore, and its first new turn is accepted. Under plan B only: that block is archived as <thinking> text with no signature, and an untouched thinking block keeps its signature.
  5. After teamai session push --scrub, rg -F "$HOME" <archive dir> and rg "/Users/|/home/|C:\\\\Users\\\\|claude-[0-9]+/" <archive dir> find nothing for a fixture covering every row of the Slice 3 table.
  6. Scoring a native session still attributes a team skill read through its absolute SKILL.md path.
  7. A tool call whose arguments contain a local path and no secret is archived with the path rewritten.
  8. A session with one capture is archived with agent equal to agentOf of its captures and teamRevision in its index entry. A session with no captures has neither.
  9. score --team-session on a pushed session with agent gives a label with that agent. One without agent still gives a label without it.
  10. An archive written before this change (absolute cwd, no agent) still lists, restores, resumes and scores.

Definition of done

  • Acceptance criteria covered by tests in src/__tests__/ (Vitest), through the public seams: SyncManager, scrubSession, AgentSession, openSessionArchive, the session and score commands.
  • Slice 2's check recorded in the PR: its result, the Claude Code version, and which plan shipped. Then push a real Claude Code session with --scrub, resume it on another directory, and confirm the first turn after resume is accepted (no signature error).
  • Docs updated in docs/guide/ and docs/guide/zh-CN/: scorers.md (the in /home/alice/app examples at :203 and :211, and :225), sharing.md (what a published session keeps).
  • npm run lint, npm run typecheck, npm test green.

Out of scope

  • Rewriting archives already on teamai-reports. They are fixed when re-pushed, and git history keeps old content regardless.
  • Refusing a push on local paths, as local_path_detected does for run snapshots: almost every transcript would be refused.
  • Removing signatures from untouched thinking blocks.
  • Dashboard views for archived sessions by team revision. This issue records the data.
  • Archive identity for sessions outside any repo (_unattributed).
  • Captures for sessions teamai launched.
  • The fallback to the CLI's directory when session.cwd is empty (archive.ts:159, session-cmd.ts:273, session-migrate-cmd.ts:482), beyond what Slice 1's rules already cover.

Related

#1176 (agent observation), #1178 (session captures), #913 (workspace children)

Dominant language
TypeScript
Stars
5.7k
Forks
486
Avg merge
19h
Merged PRs (30d)
415

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Tencent/teamai-cli

All issues in Tencent/teamai-cli

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.