Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

A session that goes quiet ends after 30 minutes, not 2 hours (SESSION_TIMEOUT_MS): a Claude Code tab left open showed under Live now as Idle for up to 2 hours after it was last used. Ending this way is the soft end it always was: the sweep marks it timed_out, and the next heartbeat, prompt or tool call in the same harness session brings it back with everything it recorded, announced as session.started again. Idle still begins at 15 minutes. The API's and MCP tool's descriptions, the OpenAPI document and the docs (sessions, CLI, Claude Code plugin, architecture, start and update session) say 30 minutes, and that a heartbeat brings a timed-out session back. Deploy: work, api. Checked: cargo test -p g1t-contracts --lib coordination (16); cargo test -p g1t-work --lib coordination (24).

G1t-Session:
ses_01m4zzpc96ect8d41mffgr1a45
syntaqxcommitted Parentfb195f8Browse files
10 files+15−130/10 viewed
+1−1
9797 pub fn description(self) -> &'static str {
9898 match self {
9999 CoordinationOp::StartSession => "Start a session: one run of your own harness (a Claude Code session, a Codex run) on a repository, so others can see who is working on what. `harness` is claude_code, codex, cursor, copilot or other. Give `harness_session_id`, the harness's own id for the session, and starting again with it returns the session already started (refreshing the issue, pull request, branch, title and head given) instead of a new one, so a hook that fires twice is harmless. `issue` and `pull` attach it to an issue and a pull request of the repository; a pull request for an issue attaches that issue too. A subagent is a session of its own: give `parent_id` (one of your sessions here), its `agent_type`, the `task` and `prompt` its parent gave it, and as `harness_session_id` the parent's harness id, `/agent/` and the subagent's id; it starts on the parent's issue, pull request and branch unless it says otherwise, sees and is seen by its siblings in get_work and list_sessions, and once ended stays ended when started again. Returns the session (`id` `ses_…`, `state` active, idle or ended). A new session, or one started again after it ended, is a session.started event. Needs a confirmed email address and a role that may open issues.",
100− CoordinationOp::UpdateSession => "Update one of your sessions: attach it to an issue or pull request, or change its branch, head commit or title. Only the fields given change. An empty body is a heartbeat: every call keeps the session active and renews the claims made from it. A session reads as idle after 15 minutes without one and ends (`end_reason` timed_out) after 2 hours; a heartbeat brings a timed-out one back. Attaching a pull request moves the entries recorded so far into that pull request's session. A subagent's `task`, `prompt`, `model` and `result` (what it reported, up to 8,000 characters) can be set too; a subagent that ended takes them and stays ended. Returns the session; one ended with end_session is refused with `409`.",
100+ CoordinationOp::UpdateSession => "Update one of your sessions: attach it to an issue or pull request, or change its branch, head commit or title. Only the fields given change. An empty body is a heartbeat: every call keeps the session active and renews the claims made from it. A session reads as idle after 15 minutes without one and ends (`end_reason` timed_out) after 30 minutes; a heartbeat brings a timed-out one back. Attaching a pull request moves the entries recorded so far into that pull request's session. A subagent's `task`, `prompt`, `model` and `result` (what it reported, up to 8,000 characters) can be set too; a subagent that ended takes them and stays ended. Returns the session; one ended with end_session is refused with `409`.",
101101 CoordinationOp::EndSession => "End one of your sessions, with an optional `reason` and `result` (what it reported when it finished: a subagent's final message, up to 8,000 characters, never a tool's output). A session.ended event. Ending one already ended returns it as it is, taking `result` if it had none. Its subagents still going end with it (`parent ended`). Its claims are not released: release them, hand the issue off, or let them expire.",
102102 CoordinationOp::GetSession => "A session and its entries, oldest first, numbered within the session (`seq`); `after` gives the entries after that number, 500 at a time. With them: `children`, its subagents and theirs in the order they started (each names its `parent_id`); `touched`, the files, commits, branches, pull requests and issues it and they touched; `links`, the issues and pull requests they worked on and how each was tied (`via`: attached, claim, branch, commit, trailer, opened or ref); `skills`, the skills they used by name; and for a subagent, `root`, the session at the top of its tree. `updates` contains up to 100 oldest pending coordination events that concern this session (id, kind, data, at): those about the issues and pull requests it is on or tied to, or every one while it is on nothing yet; never its own start, end or claims. Reading never acknowledges them; use record_session_entries with acknowledge IDs after considering them. To check for updates without the transcript, give `after` 4294967295. Anyone who can read the repository may.",
103103 CoordinationOp::ListSessions => "A repository's sessions, most recently heard from first, up to 100: `state` active (heard from in the last 15 minutes), idle or ended; `issue` or `pull` for those on one, attached or tied to it any other way; `path` for those that touched a file (or any under a folder ending in `/`). Subagents are left out unless `subagents` is true, which lists each tree whole. Each says how many `subagents` it started. Anyone who can read the repository may.",
+1−1
210210 Tool {
211211 name: "session",
212212 title: "Sessions",
213− description: "Your harness session on a repository, so others see who is working on what: `start` once (idempotent on harness_session_id; a subagent starts its own with parent_id), `update` as a heartbeat or to attach an issue, pull request, branch or head commit, `record` user-facing decisions, tool calls, skills used and what it touched (idempotent on key), `end` when done with what it reported. `list` with an issue, or a path, shows who else (sibling subagents too) is on the same work. `dispatch` starts work on an issue in a session on the person's own Claude. A session goes idle after 15 minutes without a heartbeat and ends after 2 hours.",
213+ description: "Your harness session on a repository, so others see who is working on what: `start` once (idempotent on harness_session_id; a subagent starts its own with parent_id), `update` as a heartbeat or to attach an issue, pull request, branch or head commit, `record` user-facing decisions, tool calls, skills used and what it touched (idempotent on key), `end` when done with what it reported. `list` with an issue, or a path, shows who else (sibling subagents too) is on the same work. `dispatch` starts work on an issue in a session on the person's own Claude. A session goes idle after 15 minutes without a heartbeat and ends after 30 minutes; a heartbeat brings it back.",
214214 default_action: None,
215215 actions: &[
216216 a("start", Op::Coordination(CoordinationOp::StartSession), "Start one, or get the one started with this harness_session_id"),
+1−1
5252 - **Nothing waits on a harness.** No cron sweeps for quiet sessions or
5353 stale claims. The next read or change of a repository's coordination
5454 finds them (`sweep`) and announces them then: a session quiet for 15
55− minutes is idle, one quiet for 2 hours has ended, and a claim not renewed
55+ minutes is idle, one quiet for 30 minutes has ended, and a claim not renewed
5656 within its time (30 minutes unless asked, at most 240) has expired.
5757 - **Everything can be sent twice.** Starting a session is idempotent on the
5858 harness's own session id, and each entry on its `key`, so a hook can call
+3−2
113113 - your files, environment or transcript.
114114
115115 A session that stops sending heartbeats reads as idle after 15 minutes and
116−ended after two hours, so a closed laptop does not leave work looking
117−claimed.
116+ended after 30 minutes, so a closed laptop does not leave work looking
117+claimed. Ended that way it is not gone: the next prompt or tool call in
118+the same Claude Code session brings it back, with everything it recorded.
118119
119120 The hooks return at once: the CLI calls run in a background process, so a
120121 slow network never holds Claude up.
+1−1
420420 | Command | What it does |
421421 | --- | --- |
422422 | `g1t session start` | Starts a session and prints its id. `--harness` (`claude_code`, `codex`, `cursor`, `copilot` or `other`; `claude-code` works too), `--harness-session-id`, `--issue`, `--pull`, `--branch` (the current one by default) and `--title`. Starting again with the same `--harness-session-id` returns the same session. For a subagent, `--parent` (the session that started it), `--agent-type`, `--task`, `--prompt` and `--model`; a subagent's session is not kept as the working copy's (nor is one with `--no-remember`). |
423−| `g1t session heartbeat` | Says the session is still working, and renews its claims. A session with no heartbeat for 15 minutes reads as idle, and after 2 hours as ended. |
423+| `g1t session heartbeat` | Says the session is still working, and renews its claims. A session with no heartbeat for 15 minutes reads as idle, and after 30 minutes as ended; a heartbeat brings it back. |
424424 | `g1t session log TEXT` | Records a note. `--kind` takes `note`, `prompt`, `message`, `tool_call` (or `tool-call`) or `skill` (a skill's name), `--tool` names the tool, and `--key` makes recording the same entry twice do nothing. `-` reads the text from stdin; put `--` before text that starts with a dash. |
425425 | `g1t session attach` | Points the session at an `--issue`, a `--pull`, a `--branch` or its latest `--head-commit`, or gives it a `--title`: what it is for, which replaces the title it had. |
426426 | `g1t session touch` | Says what the session touched: `--file PATH`, `--commit SHA`, `--branch NAME`, `--pull N` (one it opened), `--issue N`, each repeatable. Ties the session to the pull requests and issues they lead to; a file another active session touched too is an `overlap.detected` for both. |
+2−2
153153 | --- | --- |
154154 | `active` | Heard from in the last 15 minutes. |
155155 | `idle` | Not heard from for 15 minutes. It may still come back. |
156−| `ended` | Ended with `end_session`, or not heard from for 2 hours (`end_reason` `timed_out`). A heartbeat brings a timed-out session back; an ended one stays ended. |
156+| `ended` | Ended with `end_session`, or not heard from for 30 minutes (`end_reason` `timed_out`). A heartbeat brings a timed-out session back; an ended one stays ended. |
157157
158158 Entries of a session attached to a pull request show in that pull
159159 request's [session](/guides/why-blame/#sessions), beside what was recorded
490490 | Event | When |
491491 | --- | --- |
492492 | `session.started` | A session (or a subagent) starts, or one that ended starts again. Not on a repeated start. |
493−| `session.ended` | A session ends, or times out after 2 hours without a heartbeat. |
493+| `session.ended` | A session ends, or times out after 30 minutes without a heartbeat. |
494494 | `session.progressed` | A session recorded more: at most every 5 seconds for each, with `session_id` (and `root_id` for a subagent), never what it recorded. Pages showing the session follow it with this. |
495495 | `work.claimed` | Someone claims an issue. `others` lists the claims already on it; `took_over` names a hand-off it takes. |
496496 | `work.released` | A claim is released, or its issue closed (`reason` `issue_closed`). |
+0−0

Binary or large file; its contents are not shown.

+4−3
2525
2626 /// A session with no heartbeat for this long reads as idle.
2727 pub const SESSION_IDLE_MS: u64 = 15 * 60 * 1000;
28−/// A session with no heartbeat for this long has ended (`timed_out`).
29−pub const SESSION_TIMEOUT_MS: u64 = 2 * 60 * 60 * 1000;
28+/// A session with no heartbeat for this long has ended (`timed_out`); a
29+/// heartbeat brings it back, so this only says when it stops showing as live.
30+pub const SESSION_TIMEOUT_MS: u64 = 30 * 60 * 1000;
3031 /// How long a claim lasts unless renewed, in minutes.
3132 pub const CLAIM_DEFAULT_MINUTES: u32 = 30;
3233 /// The longest a claim may be asked to last, in minutes.
104105 Active,
105106 /// Not heard from for 15 minutes; it may still come back.
106107 Idle,
107− /// Ended, or not heard from for 2 hours (`end_reason` `timed_out`).
108+ /// Ended, or not heard from for 30 minutes (`end_reason` `timed_out`).
108109 Ended,
109110 }
110111
+1−1
207207 Ok(Outcome::Ok(repo))
208208 }
209209
210− /// Ends the repository's sessions that have gone two hours without a
210+ /// Ends the repository's sessions that have gone 30 minutes without a
211211 /// heartbeat (`timed_out`) and expires its claims past their time, and
212212 /// announces each. Run before the repository's coordination is read or
213213 /// changed, so that nobody sees a stale one as live.
+1−1
166166 fn a_quiet_session_times_out_and_a_heartbeat_brings_it_back() {
167167 let db = database();
168168 start(&db, "ses_1", Some("a"));
169− // Two hours and more without a heartbeat.
169+ // 30 minutes and more without a heartbeat.
170170 let late = rfc3339(NOW + g1t_contracts::work::SESSION_TIMEOUT_MS);
171171 let ended: Vec<String> = db
172172 .prepare(&time_out_sessions_sql())

Comments

Sign in to comment.