G6: context-save computes session duration on Linux (#2704)

The duration step parsed 'ps -o lstart=' with BSD-only 'date -jf', so on
Linux session_duration_s was always unknown. It now falls back to GNU
'date -d', and never parses an empty start (GNU reads that as midnight).
Ported onto the current block instead of #2704's ~/.gstack/sessions
mtime, which bypassed the state root.

Co-authored-by: Evgenii Lopatin <e75533@gmail.com>
This commit is contained in:
garrytanandEvgenii Lopatin committed 2026-10-03 21:13:53 +00:00
1 parent 5ee77d2ec4
commit e2cf27ff5e
2 files changed
+30 -1

No files matched your search

+4 -1
View File
@@ -94,7 +94,10 @@ Try to determine how long this session has been active:
if [ -n "$_TEL_START" ]; then
START_EPOCH="$_TEL_START"
elif [ -n "$PPID" ]; then
START_EPOCH=$(ps -o lstart= -p $PPID 2>/dev/null | xargs -I{} date -jf "%c" "{}" "+%s" 2>/dev/null || echo "")
_LSTART=$(ps -o lstart= -p "$PPID" 2>/dev/null)
# BSD date (macOS) parses with -jf; GNU date (Linux) with -d. Never parse an
# empty start: GNU date -d "" means today at midnight.
[ -n "$_LSTART" ] && START_EPOCH=$(date -jf "%c" "$_LSTART" "+%s" 2>/dev/null || date -d "$_LSTART" "+%s" 2>/dev/null || echo "")
fi
if [ -n "$START_EPOCH" ]; then
NOW=$(date +%s)
+26
View File
@@ -432,3 +432,29 @@ describe('migration v1.1.3.0: HOME guard', () => {
expect(result.stdout.toString().trim()).toBe('');
});
});
// G6 rider #2704: the duration block parsed `ps -o lstart=` with BSD-only
// `date -jf`, so on Linux session_duration_s was always "unknown". Runs the
// block extracted from the template, so it cannot drift from the skill.
describe('context-save session duration (#2704)', () => {
const tmpl = fs.readFileSync(path.join(ROOT, 'context-save', 'SKILL.md.tmpl'), 'utf-8');
const step = tmpl.slice(tmpl.indexOf('### Step 3: Compute session duration'));
const block = step.match(/```bash\n([\s\S]*?)\n```/)![1];
const psWorks = spawnSync('bash', ['-c', 'ps -o lstart= -p $$'], { encoding: 'utf-8', timeout: 5000 }).stdout.trim() !== '';
test.skipIf(!psWorks)('computes a numeric duration from the parent process start time', () => {
const r = spawnSync('bash', ['-c', block], { env: { PATH: process.env.PATH! }, encoding: 'utf-8', timeout: 10_000 });
expect(r.stdout).toMatch(/^SESSION_DURATION_S=\d+$/m);
});
test('an unreadable parent start says unknown instead of inventing midnight', () => {
const shim = fs.mkdtempSync(path.join(os.tmpdir(), 'ctxsave-ps-'));
try {
fs.writeFileSync(path.join(shim, 'ps'), '#!/bin/sh\nexit 1\n', { mode: 0o755 });
const r = spawnSync('bash', ['-c', block], { env: { PATH: `${shim}:${process.env.PATH}` }, encoding: 'utf-8', timeout: 10_000 });
expect(r.stdout.trim()).toBe('SESSION_DURATION_S=unknown');
} finally {
fs.rmSync(shim, { recursive: true, force: true });
}
});
});