Skip to content

feat(claude-code): add transcript_retention_days and Stop-hook idle sentinel#866

Open
morganl-ant wants to merge 1 commit intocoder:mainfrom
morganl-ant:anthropic/session-lifecycle
Open

feat(claude-code): add transcript_retention_days and Stop-hook idle sentinel#866
morganl-ant wants to merge 1 commit intocoder:mainfrom
morganl-ant:anthropic/session-lifecycle

Conversation

@morganl-ant
Copy link
Copy Markdown
Contributor

@morganl-ant morganl-ant commented Apr 22, 2026

Problem

Two install-time lifecycle gaps in the v5 module:

  • Session JSONL transcripts under ~/.claude/projects/ are never pruned by the module, so long-lived workspaces accumulate unbounded transcript files unless the user knows to set cleanupPeriodDays themselves.
  • There is no machine-readable signal that Claude has gone idle, which makes it hard for templates to drive Coder's workspace autostop or activity tracking off agent state.

Changes

  • New transcript_retention_days input (number, default null, validated >= 1). When set, the module writes cleanupPeriodDays to a managed-settings drop-in so Claude Code prunes old transcripts automatically. When unset, Claude Code's built-in 30-day default applies.
  • The same drop-in registers a Stop hook that touches ~/.coder-modules/coder/claude-code/last-stop whenever Claude finishes a turn. Templates can read that file's mtime to drive autostop or activity bumping.
  • Both are written to /etc/claude-code/managed-settings.d/30-coder-lifecycle.json. This is the local managed-settings file mechanism, read by the Claude CLI regardless of inference backend (Anthropic API, Bedrock, Vertex, AI Gateway). If /etc/claude-code is not writable and sudo is unavailable, install logs a warning and continues.
  • Tests: lifecycle-settings-written (retention set) and lifecycle-settings-default-retention (Stop hook present, no cleanupPeriodDays).
  • README: new "Session lifecycle" section; version bumped to 5.1.0.

Relationship to #861

The original revision of this PR also replaced the hardcoded TASK_SESSION_ID in start.sh with a workspace-derived UUIDv5 (#726). #861 removed start.sh and module-managed sessions entirely, which resolves #726 directly: there is no longer a hardcoded session ID because the module no longer starts Claude. This PR is now reduced to the install-time pieces above.

Validation

  • terraform fmt clean
  • terraform validate clean
  • terraform test 14/14 pass
  • bun test 14/14 pass locally (one pre-existing docker-cleanup race in claude-mcp-config is flaky in full-suite runs but passes in isolation; unrelated to this change)

Disclosure: I work on Claude Code at Anthropic. This is one of a small set of contributions we are proposing to the claude-code module; happy to discuss the overall direction in whichever venue the maintainers prefer.

@morganl-ant morganl-ant marked this pull request as ready for review April 22, 2026 20:57
@matifali matifali added the version:patch Add to PRs requiring a patch version upgrade label Apr 23, 2026
Copy link
Copy Markdown
Member

@matifali matifali left a comment

Choose a reason for hiding this comment

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

@morganl-ant thanks for your contribution. I think we can merge this to main and release a new patch version to unblock your users. Bit for other PRs, let's rebase them once #861 (likely today) lands.

@matifali matifali requested a review from 35C4n0r April 23, 2026 12:44
@matifali
Copy link
Copy Markdown
Member

Also, you would need to bump the patch version in README.md examples and fix the failing tests.

@morganl-ant
Copy link
Copy Markdown
Contributor Author

Thanks, both addressed in 11925ea:

  • README examples bumped 4.9.2 -> 4.9.3.
  • Test failures were two separate bugs: (1) the deriveTaskSessionId helper tried to grep the workspace ID out of script.sh, but the install/start scripts are base64-encoded inside the agentapi wrapper so the regex never matched and the helper silently returned the legacy constant. Fixed by pinning CODER_WORKSPACE_ID in the test env so the derived session ID is deterministic. (2) configure_lifecycle_settings was called after report_tasks in install.sh; report_tasks shells out to the coder CLI which is not in the test container, and set -e aborted before the lifecycle file was written. Swapped the order.

Full suite is 23/23 locally now.

…entinel

Re-authored on top of coder#861. The original PR also fixed the hardcoded
TASK_SESSION_ID in start.sh (coder#726); coder#861 removed start.sh entirely so
that fix is no longer needed and coder#726 is resolved by coder#861 itself. What
remains is install-time:

- transcript_retention_days input maps to Claude Code's
  cleanupPeriodDays setting via a managed-settings.d drop-in so
  long-lived workspaces do not accumulate unbounded session JSONL.
- A Stop hook touches ~/.coder-modules/coder/claude-code/last-stop on
  every turn end so templates can drive workspace autostop or activity
  tracking off that file's mtime.

Both are written to /etc/claude-code/managed-settings.d/30-coder-lifecycle.json,
which the Claude CLI reads regardless of inference backend.
@morganl-ant morganl-ant force-pushed the anthropic/session-lifecycle branch from 11925ea to 108d41f Compare April 27, 2026 23:29
@morganl-ant morganl-ant changed the title fix(claude-code): derive task session ID from workspace; add transcript_retention_days; emit Stop-hook idle sentinel feat(claude-code): add transcript_retention_days and Stop-hook idle sentinel Apr 27, 2026
@morganl-ant
Copy link
Copy Markdown
Contributor Author

Rebased onto post-#861 main. The session-ID derivation half of the original PR fell away with #861's removal of start.sh (which also resolves #726 directly). What remains is the install-time transcript_retention_days -> cleanupPeriodDays mapping and the Stop-hook idle sentinel. Title and description updated to match.

Copy link
Copy Markdown
Member

@matifali matifali left a comment

Choose a reason for hiding this comment

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

Claude Code is very fast-paced. And we see new features and settings being added every other week. Do you think we should have a more flexible generic way to configure it than adding new named module inputs for each new feature?

For example, https://code.claude.com/docs/en/settings shows many options that can be useful for Enterprises, but adding all of them as named modules does not seem scalable and maintainable to me.

Curious about what you think about this?

Otherwise, I am happy to accept this. One reason for the larger refactor we did in v5.0.0 was to increase the flexibility of configuration so we do not limit users on how they want to use Claude code in a coder workspace.

Thanks

local target="$${dropin_dir}/30-coder-lifecycle.json"
# Bake the absolute path at install time so the hook does not depend on
# $HOME being set identically when Claude Code executes it.
local sentinel="$HOME/.coder-modules/coder/claude-code/last-stop"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should depend on module_dir_name input. Although we have a very strict regex there, but hardcoding this would be less flexible for any future changes.


The module writes a small managed-settings drop-in to `/etc/claude-code/managed-settings.d/30-coder-lifecycle.json` that:

- registers a `Stop` hook which touches `~/.coder-modules/coder/claude-code/last-stop` whenever Claude finishes a turn. Templates can read that file's modification time to drive workspace autostop or activity tracking.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we elaborate a bit more on how templates can use this for activity tracking?

}
```

The drop-in is a local file read by the Claude CLI at startup; it works with any inference backend (Anthropic API, Bedrock, Vertex, AI Gateway). If `/etc/claude-code` is not writable in the workspace image and `sudo` is unavailable, the install script logs a warning and skips the write.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we, in this case, write to the user's $HOME, e.g., $HOME/.claude/settings.json?

@DevelopmentCats
Copy link
Copy Markdown
Collaborator

@matifali

I agree with you here. I think we can close this because this amounts to configuration that can now be done through managed_settings introduced in #863

@matifali
Copy link
Copy Markdown
Member

@DevelopmentCats I would wait for what @morganl-ant thinks. So maybe keep it open for now.

@matifali matifali reopened this Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

version:patch Add to PRs requiring a patch version upgrade

Projects

None yet

Development

Successfully merging this pull request may close these issues.

claude-code: stale session state causes "Session ID already in use" after workspace restart

3 participants