Skip to content

fix(sessions): link a pull request to a session only on its own evidence - #14650

Merged
iscekic merged 4 commits into
mainfrom
kwf/owner-pr-session-link-kilocode-20260929
Sep 29, 2026
Merged

iscekic merged 4 commits into
mainfrom
kwf/owner-pr-session-link-kilocode-20260929

Conversation

@iscekic

@iscekic iscekic commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Fix proof

[C1] a mentioned, listed or viewed PR is never a link and the mention scrapers are gone

Asserted value: mention-proof: linked=false. Sense check (model): Head log line 'mention-proof: linked=false' names the asserted value and the test 'a mentioned, listed or viewed PR never links' passes (1 pass, 0 fail), with no base run provided so the head is judged alone.

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 8afa453076e8 exited 0
$ cd packages/opencode && KWF_PROOF_NS="$(printf %s "$KWF_SCENARIO_NAMESPACE" | sha256sum | cut -c1-12)" XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-mention.test.ts
bun test v1.3.14 (0d9b296a)
mention-proof: linked=false
test/kilocode/sessions/pr-link-proof-mention.test.ts:
(pass) mention is not evidence > a mentioned, listed or viewed PR never links [2959.41ms]
 1 pass
 0 fail
 6 expect() calls
Ran 1 test across 1 file. [4.55s]

[C2] a same-named branch in another repo or a fork never links

Asserted value: crossrepo-proof: other=false fork=false. Sense check (model): Head assertion exits 0 and its log line 'crossrepo-proof: other=false fork=false' names the asserted value, with '(pass) ... a same-named branch in another repo or a fork never links'.

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert a43fcfefc69a exited 0
$ cd packages/opencode && KWF_PROOF_NS="$(printf %s "$KWF_SCENARIO_NAMESPACE" | sha256sum | cut -c1-12)" XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-repo.test.ts
bun test v1.3.14 (0d9b296a)
crossrepo-proof: other=false fork=false
test/kilocode/sessions/pr-link-proof-repo.test.ts:
(pass) the link must name the session own repository > a same-named branch in another repo or a fork never links [2930.68ms]
 1 pass
 0 fail
 2 expect() calls
Ran 1 test across 1 file. [4.33s]

[C3] a reused branch name does not inherit the legacy per-worktree record

Asserted value: reused-branch-proof: inherited=false pruned=true removed=true. Sense check (model): head log line "reused-branch-proof: inherited=false pruned=true removed=true" matches the asserted value with a (pass) and 1 pass / 0 fail; no base run is given, so the head alone is judged

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 45218d1e3cf6 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-reused.test.ts
reused-branch-proof: inherited=false pruned=true removed=true
test/kilocode/sessions/pr-link-proof-reused.test.ts:
INFO  2026-09-29T08:22:07 +2639ms service=pr-link count=1 pruned legacy worktree PR links
(pass) a reused branch name keeps no old link > the legacy worktree record is ignored and pruned [2509.77ms]
 1 pass
 0 fail
 3 expect() calls
Ran 1 test across 1 file. [3.91s]

[C4] the poll never discovers a link from a branch name alone

Asserted value: poll-proof: calls=0 discovered=0. Sense check (model): head log line 'poll-proof: calls=0 discovered=0' names the asserted value and test '...never links' passed with exit 0

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 1fe8ff9d483c exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-poll.test.ts
bun test v1.3.14 (0d9b296a)
poll-proof: calls=0 discovered=0
test/kilocode/sessions/pr-link-proof-poll.test.ts:
(pass) the poll only refreshes a link a session owns > an open PR for a branch a session only checked out never links [2441.92ms]
 1 pass
 0 fail
 2 expect() calls
Ran 1 test across 1 file. [3.87s]

[C5] an explicit kilo pr link is scoped to one session and refuses another repo

Asserted value: cli-pr-proof: no-session-error=true. Sense check (model): Head log names the asserted value 'cli-pr-proof: no-session-error=true' and its head assertions exit 0 with '(pass) ... an explicit link for another repository is refused'.

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 481ab257ad51 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/cli/pr-link-proof-session.test.ts
cli-pr-proof: no-session-error=true
Linked PR #55 (github)
https://github.com/owner/repo/pull/55
(pass) pr link is scoped to a session > an explicit link is recorded for the named session only [1.85ms]
(pass) pr link is scoped to a session > an explicit link for another repository is refused [0.69ms]
 3 pass
 0 fail
 7 expect() calls
Ran 3 tests across 1 file. [2.71s]

[C6] the heartbeat and ingest carry a per-session link with headRef/headSha, no fan-out, and all-null clears

Asserted value: heartbeat-proof: owner-linked=true bystander-linked=false owner-headRef=feature/x owner-headSha=true. Sense check (model): Head assertion exited 0 and its log line 'heartbeat-proof: owner-linked=true bystander-linked=false owner-headRef=feature/x owner-headSha=true' exactly names the asserted value; no base run given, so the head is judged alone.

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 3da0abfc31d2 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/pr-link-proof-heartbeat.test.ts
heartbeat-proof: owner-linked=true bystander-linked=false owner-headRef=feature/x owner-headSha=true
INFO  2026-09-29T08:22:38 +0ms service=bus type=memory.updated subscribing
(pass) heartbeat per-session PR link > upgrade sends one clear for an inherited worktree link [2902.92ms]
ERROR 2026-09-29T08:22:38 +44ms service=kilocode-indexing err=All fibers interrupted without error failed to initialize indexing
WARN  2026-09-29T08:22:38 +144ms service=kilocode-bootstrap err=All fibers interrupted without error indexing bootstrap failed
 4 pass
 0 fail
 23 expect() calls
Ran 4 tests across 1 file. [16.10s]

Fix proof

[C1] a mentioned, listed or viewed PR is never a link and the mention scrapers are gone

Asserted value: mention-proof: linked=false. Sense check (model): head log line 'mention-proof: linked=false' names the asserted value with the test passing (1 pass, 0 fail)

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 8afa453076e8 exited 0
$ cd packages/opencode && KWF_PROOF_NS="$(printf %s "$KWF_SCENARIO_NAMESPACE" | sha256sum | cut -c1-12)" XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-mention.test.ts
bun test v1.3.14 (0d9b296a)
mention-proof: linked=false
test/kilocode/sessions/pr-link-proof-mention.test.ts:
(pass) mention is not evidence > a mentioned, listed or viewed PR never links [2399.59ms]
 1 pass
 0 fail
 6 expect() calls
Ran 1 test across 1 file. [3.76s]

[C2] a same-named branch in another repo or a fork never links

Asserted value: crossrepo-proof: other=false fork=false. Sense check (model): head log line 'crossrepo-proof: other=false fork=false' names the asserted value with the test exit 0 and 1 pass, and with no base run the head is judged alone

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert a43fcfefc69a exited 0
$ cd packages/opencode && KWF_PROOF_NS="$(printf %s "$KWF_SCENARIO_NAMESPACE" | sha256sum | cut -c1-12)" XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-repo.test.ts
bun test v1.3.14 (0d9b296a)
crossrepo-proof: other=false fork=false
test/kilocode/sessions/pr-link-proof-repo.test.ts:
(pass) the link must name the session own repository > a same-named branch in another repo or a fork never links [2817.98ms]
 1 pass
 0 fail
 2 expect() calls
Ran 1 test across 1 file. [4.17s]

[C3] a reused branch name does not inherit the legacy per-worktree record

Asserted value: reused-branch-proof: inherited=false pruned=true removed=true. Sense check (model): head log line 'reused-branch-proof: inherited=false pruned=true removed=true' matches the asserted value and the head assertion exited 0 (no base run, judged on head alone)

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 45218d1e3cf6 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-reused.test.ts
reused-branch-proof: inherited=false pruned=true removed=true
test/kilocode/sessions/pr-link-proof-reused.test.ts:
INFO  2026-09-29T07:38:28 +2471ms service=pr-link count=1 pruned legacy worktree PR links
(pass) a reused branch name keeps no old link > the legacy worktree record is ignored and pruned [2352.81ms]
 1 pass
 0 fail
 3 expect() calls
Ran 1 test across 1 file. [3.69s]

[C4] the poll never discovers a link from a branch name alone

Asserted value: poll-proof: calls=0 discovered=0. Sense check (model): Head log line 'poll-proof: calls=0 discovered=0' names the asserted value and the passing test asserts the poll never links a PR for a branch a session only checked out.

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 1fe8ff9d483c exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-poll.test.ts
bun test v1.3.14 (0d9b296a)
poll-proof: calls=0 discovered=0
test/kilocode/sessions/pr-link-proof-poll.test.ts:
(pass) the poll only refreshes a link a session owns > an open PR for a branch a session only checked out never links [2356.41ms]
 1 pass
 0 fail
 2 expect() calls
Ran 1 test across 1 file. [3.70s]

[C5] an explicit kilo pr link is scoped to one session and refuses another repo

Asserted value: cli-pr-proof: no-session-error=true. Sense check (model): Head log line 'cli-pr-proof: no-session-error=true' matches the asserted value, with both scoping tests passing; no base run so judged on the head alone.

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 481ab257ad51 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/cli/pr-link-proof-session.test.ts
cli-pr-proof: no-session-error=true
Linked PR #55 (github)
https://github.com/owner/repo/pull/55
(pass) pr link is scoped to a session > an explicit link is recorded for the named session only [1.72ms]
(pass) pr link is scoped to a session > an explicit link for another repository is refused [0.59ms]
 3 pass
 0 fail
 7 expect() calls
Ran 3 tests across 1 file. [2.54s]

[C6] the heartbeat and ingest carry a per-session link with headRef/headSha, no fan-out, and all-null clears

Asserted value: heartbeat-proof: owner-linked=true bystander-linked=false owner-headRef=feature/x owner-headSha=true. Sense check (model): Head assertion exited 0 and its log line 'heartbeat-proof: owner-linked=true bystander-linked=false owner-headRef=feature/x owner-headSha=true' exactly names the asserted value.

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 3da0abfc31d2 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/pr-link-proof-heartbeat.test.ts
heartbeat-proof: owner-linked=true bystander-linked=false owner-headRef=feature/x owner-headSha=true
INFO  2026-09-29T07:38:57 +0ms service=bus type=memory.updated subscribing
(pass) heartbeat per-session PR link > upgrade sends one clear for an inherited worktree link [2754.41ms]
ERROR 2026-09-29T07:38:57 +42ms service=kilocode-indexing err=All fibers interrupted without error failed to initialize indexing
WARN  2026-09-29T07:38:57 +151ms service=kilocode-bootstrap err=All fibers interrupted without error indexing bootstrap failed
 4 pass
 0 fail
 23 expect() calls
Ran 4 tests across 1 file. [15.35s]

Fix proof

[C1] a mentioned, listed or viewed PR is never a link and the mention scrapers are gone

Asserted value: mention-proof: linked=false. Sense check (model): head log line 'mention-proof: linked=false' matches the asserted value and the head assert exited 0 with 1 pass

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 8afa453076e8 exited 0
$ cd packages/opencode && KWF_PROOF_NS="$(printf %s "$KWF_SCENARIO_NAMESPACE" | sha256sum | cut -c1-12)" XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-mention.test.ts
bun test v1.3.14 (0d9b296a)
mention-proof: linked=false
test/kilocode/sessions/pr-link-proof-mention.test.ts:
(pass) mention is not evidence > a mentioned, listed or viewed PR never links [2748.52ms]
 1 pass
 0 fail
 6 expect() calls
Ran 1 test across 1 file. [4.27s]

[C2] a same-named branch in another repo or a fork never links

Asserted value: crossrepo-proof: other=false fork=false. Sense check (model): head log line 'crossrepo-proof: other=false fork=false' matches the asserted value with the test passing (1 pass, 0 fail, exit 0)

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert a43fcfefc69a exited 0
$ cd packages/opencode && KWF_PROOF_NS="$(printf %s "$KWF_SCENARIO_NAMESPACE" | sha256sum | cut -c1-12)" XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-repo.test.ts
bun test v1.3.14 (0d9b296a)
crossrepo-proof: other=false fork=false
test/kilocode/sessions/pr-link-proof-repo.test.ts:
(pass) the link must name the session own repository > a same-named branch in another repo or a fork never links [3207.63ms]
 1 pass
 0 fail
 2 expect() calls
Ran 1 test across 1 file. [4.74s]

[C3] a reused branch name does not inherit the legacy per-worktree record

Asserted value: reused-branch-proof: inherited=false pruned=true removed=true. Sense check (model): Head assertion exited 0 and its log line 'reused-branch-proof: inherited=false pruned=true removed=true' names the asserted value, with the test passing.

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 45218d1e3cf6 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-reused.test.ts
reused-branch-proof: inherited=false pruned=true removed=true
test/kilocode/sessions/pr-link-proof-reused.test.ts:
INFO  2026-09-29T06:16:16 +2762ms service=pr-link count=1 pruned legacy worktree PR links
(pass) a reused branch name keeps no old link > the legacy worktree record is ignored and pruned [2614.92ms]
 1 pass
 0 fail
 3 expect() calls
Ran 1 test across 1 file. [4.15s]

[C4] the poll never discovers a link from a branch name alone

Asserted value: poll-proof: calls=0 discovered=0. Sense check (model): head assertion exited 0 and its log line 'poll-proof: calls=0 discovered=0' matches the asserted value, judged alone as no base run is given

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 1fe8ff9d483c exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-poll.test.ts
bun test v1.3.14 (0d9b296a)
poll-proof: calls=0 discovered=0
test/kilocode/sessions/pr-link-proof-poll.test.ts:
(pass) the poll only refreshes a link a session owns > an open PR for a branch a session only checked out never links [2650.95ms]
 1 pass
 0 fail
 2 expect() calls
Ran 1 test across 1 file. [4.19s]

[C5] an explicit kilo pr link is scoped to one session and refuses another repo

Asserted value: cli-pr-proof: no-session-error=true. Sense check (model): The head log line 'cli-pr-proof: no-session-error=true' names the asserted value, and its passing assertions 'an explicit link is recorded for the named session only' and 'an explicit link for another repository is refused' show the claimed scoping and refusal.

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 481ab257ad51 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/cli/pr-link-proof-session.test.ts
cli-pr-proof: no-session-error=true
Linked PR #55 (github)
https://github.com/owner/repo/pull/55
(pass) pr link is scoped to a session > an explicit link is recorded for the named session only [2.32ms]
(pass) pr link is scoped to a session > an explicit link for another repository is refused [0.92ms]
 3 pass
 0 fail
 7 expect() calls
Ran 3 tests across 1 file. [2.85s]

[C6] the heartbeat and ingest carry a per-session link with headRef/headSha, no fan-out, and all-null clears

Asserted value: heartbeat-proof: owner-linked=true bystander-linked=false owner-headRef=feature/x owner-headSha=true. Sense check (model): head assertion exited 0 and its output line 'heartbeat-proof: owner-linked=true bystander-linked=false owner-headRef=feature/x owner-headSha=true' names the asserted value exactly; with no base run the head is judged alone.

The scripts were proven on an earlier base, so only the head ran.

Head log: backend-assert 3da0abfc31d2 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/pr-link-proof-heartbeat.test.ts
heartbeat-proof: owner-linked=true bystander-linked=false owner-headRef=feature/x owner-headSha=true
INFO  2026-09-29T06:16:48 +0ms service=bus type=memory.updated subscribing
(pass) heartbeat per-session PR link > upgrade sends one clear for an inherited worktree link [3139.54ms]
ERROR 2026-09-29T06:16:48 +56ms service=kilocode-indexing err=All fibers interrupted without error failed to initialize indexing
WARN  2026-09-29T06:16:48 +169ms service=kilocode-bootstrap err=All fibers interrupted without error indexing bootstrap failed
 4 pass
 0 fail
 23 expect() calls
Ran 4 tests across 1 file. [17.49s]

Changelog for users

  • A session lists only the pull request that session created or pushed; other sessions in the same checkout report none.
  • A PR URL that agent text only mentions, lists, views, or reviews no longer links to a session.
  • A reused branch name no longer inherits a pull request from an earlier session.
  • kilo pr link applies to one named session and refuses a pull request for another repository.
  • kilo pr unlink and kilo pr status act on one session and report no link when it owns none.
  • Checking out a colleague's branch no longer links their pull request to your session.

Changelog for maintainers

  • Session PR links are stored per session; session_pr_link and the heartbeat now carry headRef and headSha beside the three legacy keys so older backends keep working.
  • A one-time migration drops the legacy per-worktree records and sends one all-null clear; inspect the persisted marker for sessions first advertised after the prune.
  • The five-minute poll now refreshes only links a session already owns, never discovers one by branch name, and makes one host query per repository and branch group.
  • The mention scrapers and worktree/branch override writes are removed; review the create and push command detection and the pushed-SHA ancestry check.
  • kilo pr link|unlink|status and set_pr_link require a session id and fail closed without one; the link_pr tool refuses a fork or another repository.
  • Deleting a session now clears its persisted link, so a record cannot outlive its session or grow the heartbeat read.

E2E proof

[C1] a mentioned, listed or viewed PR is never a link and the mention scrapers are gone

Asserted value: mention-proof: linked=false. Sense check (jev): probability 0.94

Base log: backend-assert 8afa453076e8 exited 1
$ cd packages/opencode && KWF_PROOF_NS="$(printf %s "$KWF_SCENARIO_NAMESPACE" | sha256sum | cut -c1-12)" XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-mention.test.ts
error: expect(received).toBeUndefined()
  prNumber: 5,
}
      at <anonymous> (/tmp/kwf-script-eccb1a22945629ac13ee540ad159ef8e8a133635a6e34f23bb0a3e9700141b30/packages/opencode/test/kilocode/sessions/pr-link-proof-mention.test.ts:46:20)
(fail) mention is not evidence > a mentioned, listed or viewed PR never links [417.93ms]
 0 pass
 1 fail
 1 expect() calls
Ran 1 test across 1 file. [3.01s]
Head log: backend-assert 8afa453076e8 exited 0
$ cd packages/opencode && KWF_PROOF_NS="$(printf %s "$KWF_SCENARIO_NAMESPACE" | sha256sum | cut -c1-12)" XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-mention.test.ts
bun test v1.3.14 (0d9b296a)
mention-proof: linked=false
test/kilocode/sessions/pr-link-proof-mention.test.ts:
(pass) mention is not evidence > a mentioned, listed or viewed PR never links [2439.19ms]
 1 pass
 0 fail
 6 expect() calls
Ran 1 test across 1 file. [3.81s]

[C2] a same-named branch in another repo or a fork never links

Asserted value: crossrepo-proof: other=false fork=false. Sense check (jev): probability 0.94

Base log: backend-assert a43fcfefc69a exited 1
$ cd packages/opencode && KWF_PROOF_NS="$(printf %s "$KWF_SCENARIO_NAMESPACE" | sha256sum | cut -c1-12)" XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-repo.test.ts
error: expect(received).toBeUndefined()
  prNumber: 5,
}
      at <anonymous> (/tmp/kwf-script-dcf2128751c04e89db9c60fd5268e456a6e80414161d2c1df846f944040a0d3f/packages/opencode/test/kilocode/sessions/pr-link-proof-repo.test.ts:51:20)
(fail) the link must name the session own repository > a same-named branch in another repo or a fork never links [812.12ms]
 0 pass
 1 fail
 1 expect() calls
Ran 1 test across 1 file. [3.39s]
Head log: backend-assert a43fcfefc69a exited 0
$ cd packages/opencode && KWF_PROOF_NS="$(printf %s "$KWF_SCENARIO_NAMESPACE" | sha256sum | cut -c1-12)" XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-repo.test.ts
bun test v1.3.14 (0d9b296a)
crossrepo-proof: other=false fork=false
test/kilocode/sessions/pr-link-proof-repo.test.ts:
(pass) the link must name the session own repository > a same-named branch in another repo or a fork never links [2878.43ms]
 1 pass
 0 fail
 2 expect() calls
Ran 1 test across 1 file. [4.24s]

[C3] a reused branch name does not inherit the legacy per-worktree record

Asserted value: reused-branch-proof: inherited=false pruned=true removed=true. Sense check (jev): probability 0.93

Base log: backend-assert 45218d1e3cf6 exited 1
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-reused.test.ts
error: expect(received).toBeUndefined()
  prNumber: 1,
}
      at <anonymous> (/tmp/kwf-script-1acbe5f638a04a9eed6e22f124693ccb6563cf6e817f920c85a2328b98938e11/packages/opencode/test/kilocode/sessions/pr-link-proof-reused.test.ts:43:25)
(fail) a reused branch name keeps no old link > the legacy worktree record is ignored and pruned [2393.87ms]
 0 pass
 1 fail
 1 expect() calls
Ran 1 test across 1 file. [3.77s]
Head log: backend-assert 45218d1e3cf6 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-reused.test.ts
reused-branch-proof: inherited=false pruned=true removed=true
test/kilocode/sessions/pr-link-proof-reused.test.ts:
INFO  2026-09-29T05:29:04 +2518ms service=pr-link count=1 pruned legacy worktree PR links
(pass) a reused branch name keeps no old link > the legacy worktree record is ignored and pruned [2391.34ms]
 1 pass
 0 fail
 3 expect() calls
Ran 1 test across 1 file. [3.78s]

[C4] the poll never discovers a link from a branch name alone

Asserted value: poll-proof: calls=0 discovered=0. Sense check (jev): probability 0.92

Base log: backend-assert 1fe8ff9d483c exited 1
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-poll.test.ts
error: expect(received).toBeUndefined()
  prNumber: 5,
}
      at <anonymous> (/tmp/kwf-script-da19af860f2f2e5b69d20989a787a926be39a41a2d65d9540dae37da91a764e1/packages/opencode/test/kilocode/sessions/pr-link-proof-poll.test.ts:65:23)
(fail) the poll only refreshes a link a session owns > an open PR for a branch a session only checked out never links [2428.23ms]
 0 pass
 1 fail
 1 expect() calls
Ran 1 test across 1 file. [3.81s]
Head log: backend-assert 1fe8ff9d483c exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/sessions/pr-link-proof-poll.test.ts
bun test v1.3.14 (0d9b296a)
poll-proof: calls=0 discovered=0
test/kilocode/sessions/pr-link-proof-poll.test.ts:
(pass) the poll only refreshes a link a session owns > an open PR for a branch a session only checked out never links [2450.04ms]
 1 pass
 0 fail
 2 expect() calls
Ran 1 test across 1 file. [3.83s]

[C5] an explicit kilo pr link is scoped to one session and refuses another repo

Asserted value: cli-pr-proof: no-session-error=true. Sense check (jev): probability 0.92

Base log: backend-assert 481ab257ad51 exited 1
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/cli/pr-link-proof-session.test.ts
cli-pr-proof: no-session-error=false
64 |     console.log(`cli-pr-proof: no-session-error=${msg(err).includes('No session specified')}`)
error: expect(received).toContain(expected)
Expected to contain: "No session specified"
(fail) pr link is scoped to a session > status requires a session and never falls back to the worktree [3.18ms]
64 |     console.log(`cli-pr-proof: no-session-error=${msg(err).includes('No session specified')}`)
error: expect(received).toBe(expected)
Expected: "function"
(fail) pr link is scoped to a session > an explicit link is recorded for the named session only [0.27ms]
error: expect(received).toBe(expected)
Expected: "function"
Received: "undefined"
      at <anonymous> (/tmp/kwf-script-5ed2fedf7f8f95afda1a9dfdb14dc72c93047c099ad465e0351bbd5a7df63a89/packages/opencode/test/cli/pr-link-proof-session.test.ts:76:37)
(fail) pr link is scoped to a session > an explicit link for another repository is refused [0.25ms]
 0 pass
 3 fail
 3 expect() calls
Ran 3 tests across 1 file. [2.59s]
Head log: backend-assert 481ab257ad51 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/cli/pr-link-proof-session.test.ts
cli-pr-proof: no-session-error=true
Linked PR #55 (github)
https://github.com/owner/repo/pull/55
(pass) pr link is scoped to a session > an explicit link is recorded for the named session only [1.74ms]
(pass) pr link is scoped to a session > an explicit link for another repository is refused [0.61ms]
 3 pass
 0 fail
 7 expect() calls
Ran 3 tests across 1 file. [2.61s]

[C6] the heartbeat and ingest carry a per-session link with headRef/headSha, no fan-out, and all-null clears

Asserted value: heartbeat-proof: owner-linked=true bystander-linked=false owner-headRef=feature/x owner-headSha=true. Sense check (jev): probability 0.95

Base log: backend-assert 3da0abfc31d2 exited 1
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/pr-link-proof-heartbeat.test.ts
error: expect(received).toBe(expected)
Expected: "function"
(fail) heartbeat per-session PR link > a session own create links only it with headRef and headSha [1255.20ms]
error: expect(received).toBe(expected)
Expected: "function"
(fail) heartbeat per-session PR link > pushing new commits keeps the link and advances headSha [251.05ms]
error: expect(received).toBe(expected)
Expected: "function"
(fail) heartbeat per-session PR link > withdrawing a link ingests the all-null triple [228.09ms]
error: expect(received).toBe(expected)
Expected: "function"
INFO  2026-09-29T05:29:53 +0ms service=bus type=memory.updated subscribing
(fail) heartbeat per-session PR link > upgrade sends one clear for an inherited worktree link [215.20ms]
ERROR 2026-09-29T05:29:53 +72ms service=kilocode-indexing err=All fibers interrupted without error failed to initialize indexing
WARN  2026-09-29T05:29:53 +143ms service=kilocode-bootstrap err=All fibers interrupted without error indexing bootstrap failed
 0 pass
 4 fail
 4 expect() calls
Ran 4 tests across 1 file. [4.94s]
Head log: backend-assert 3da0abfc31d2 exited 0
$ cd packages/opencode && XDG_DATA_HOME="$(mktemp -d)" bun test test/kilocode/pr-link-proof-heartbeat.test.ts
heartbeat-proof: owner-linked=true bystander-linked=false owner-headRef=feature/x owner-headSha=true
INFO  2026-09-29T05:30:14 +1ms service=bus type=memory.updated subscribing
(pass) heartbeat per-session PR link > upgrade sends one clear for an inherited worktree link [2804.32ms]
ERROR 2026-09-29T05:30:14 +40ms service=kilocode-indexing err=All fibers interrupted without error failed to initialize indexing
WARN  2026-09-29T05:30:14 +147ms service=kilocode-bootstrap err=All fibers interrupted without error indexing bootstrap failed
 4 pass
 0 fail
 23 expect() calls
Ran 4 tests across 1 file. [15.56s]
Owner request

Kilo-Org/kilocode: the CLI reports random, unrelated pull requests as a session's PR. Make the session-to-PR link robust: a wrong link is worse than no link.

Symptom (owner report): sessions in the Kilo app (web, mobile Agents list, extension) show entirely random PRs. The CLI (kilo, also kilo serve under the VS Code extension) is the source of each session's stored link: it sends session_pr_link ingest items and the heartbeat prLink, and the backend stores them as the session's PR.

Code path in this repo that decides the link (packages/opencode/src/kilo-sessions/):

  • kilo-sessions.ts heartbeat (around const pr = await resolvePrLink() near line 954): the PR link is resolved ONCE per worktree and ingested for EVERY advertised session (for (const row of sessions) await syncPrLinkTriple(row.id, pr.triple)) and attached to every heartbeat row, even rows whose directory is another checkout and old sessions that never touched the PR. syncPrLinkForSession on Session.Event.Updated does the same for any updated session.
  • kilo-sessions.ts MessageV2.Event.PartUpdated + pr-link.ts recordPrLinkText/firstPrUrl: the FIRST PR URL of the same repo that appears in any agent text or completed tool output (gh pr list, gh pr view 123, gh search prs, a pasted link, a review of someone else's PR) is recorded as the worktree's PR and synced to the session. Mentioning a PR is treated as owning it.
  • pr-link.ts recordedLinks / persistRecordedPrLink / readRecordedPrLink: the link is stored per worktree (Storage key session_pr_link_recorded/<worktree>) and per branch name, not per session, and outlives the process, so every later session in that checkout on that branch name inherits it; a reused branch name (e.g. main, dev, fix/typo) keeps an old PR.
  • pr-link-poller.ts refreshPrLink/githubOpenPr: every 5 minutes it asks GitHub for any open PR whose head is <remote owner>:<branch> and writes it as the worktree link, so checking out a colleague's branch (to review, or a reused branch name) links THEIR PR to your session; the result again fans out to all sessions.
  • cli/cmd/pr.ts kilo pr link override is also per worktree and fans out to every session.

Required rule: a PR is linked to a session only on hard evidence, never on fuzzy matching.

  • The link is per session, never per worktree or per branch name. No fan-out: a session gets a link only from its own evidence.
  • Hard evidence = the session itself created the PR (a gh pr create / host API create run by that session whose output returned the PR URL) or the session pushed the PR's head branch: the PR's base and head repository equal the session worktree's remote repository (host + owner + name; a fork or another repo with the same branch name never matches), the PR's head ref equals the branch the session pushed, and the PR's head SHA equals (or descends from) a commit the session pushed. A PR URL that is merely mentioned, listed, viewed, or reviewed is never a link. An explicit kilo pr link <url> by the user applies to the current session only and still requires the same repo.
  • The 5-minute poll may only refresh the state of a link a session already owns; it must not discover new links by branch name alone.
  • Evidence contract shared with the Kilo-Org/cloud item: session_pr_link gains two optional fields, headRef (the branch the session pushed) and headSha (the commit the session pushed); send them with every link. A clear (all-null triple) is sent when a session's link is withdrawn. Keep the existing three keys so older backends keep working.
  • On upgrade, drop existing per-worktree recorded links (they were not per-session evidence) and send a clear for sessions whose link came only from them.

Proof required:

  • Unit tests (packages/opencode/test/kilocode/, bun test) for each false-link case: same-named branch in another repo; same-named branch in a fork; a reused branch name whose old PR must not attach to a new session; a PR opened by someone else on a branch the session only checked out (no push by the session); a PR URL only mentioned in agent text or gh pr list/gh pr view output; several sessions in one worktree where only the session that created/pushed the PR gets it and the heartbeat carries no link for the others. Plus the positive cases: a session's own gh pr create output links that PR with headRef/headSha; a session that pushes new commits to its PR keeps the link.
  • A live check with the built CLI against a real git repo: two sessions in one checkout, one creates a PR, and only that session reports it (ingest payload and heartbeat captured), the other reports none.
  • The PR description must name the root causes (worktree-wide fan-out, mention-as-ownership text scraping, per-worktree persisted record, branch-name poll) and the new linking rule.

@iscekic iscekic self-assigned this Sep 29, 2026
@iscekic

iscekic commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Fixed failing checks in fbf32ed.

@iscekic
iscekic marked this pull request as ready for review September 29, 2026 06:53
@iscekic iscekic added the merge-by-human the merge bot routed this PR to a human label Sep 29, 2026
Comment thread packages/opencode/src/kilo-sessions/pr-link.ts Outdated
Comment thread packages/opencode/src/kilo-sessions/pr-link-poller.ts Outdated
Comment thread packages/opencode/src/kilo-sessions/pr-link-poller.ts Outdated
Comment thread packages/opencode/src/kilo-sessions/kilo-sessions.ts
@kilo-code-bot

kilo-code-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The prior findings are addressed in 56ab1e502a / 3eb55b0973: dry-run/delete pushes no longer advance headSha (pushDeletesOrDryRuns), the poll no longer rewrites headSha from the host, the migration candidate that already owns a link now settles, and a mismatched host answer is surfaced via a warning.

Files Reviewed (6 files)
  • packages/opencode/src/kilo-sessions/kilo-sessions.ts
  • packages/opencode/src/kilo-sessions/pr-link-poller.ts
  • packages/opencode/src/kilo-sessions/pr-link.ts
  • packages/opencode/test/kilocode/kilo-sessions.test.ts
  • packages/opencode/test/kilocode/sessions/pr-link.test.ts
  • script/check-opencode-promise-facades.ts
Previous Review Summary (commit fbf32ed)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit fbf32ed)

Status: 4 Issues Found | Recommendation: Address before merge

The per-session PR link model correctly removes the worktree fan-out, the mention scrapers, and branch-name discovery. Findings are about evidence hardening and edge behavior, not the core design.

Fix these issues in Kilo Cloud

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilo-sessions/pr-link.ts 496 git push --dry-run / -n (and branch deletion) is treated as a push, advancing headSha to a commit that was never pushed.

SUGGESTION

File Line Issue
packages/opencode/src/kilo-sessions/pr-link-poller.ts 310 A host answer whose PR URL differs from the stored link is skipped, so a closed link can linger when another open PR exists on the same head.
packages/opencode/src/kilo-sessions/pr-link-poller.ts 312 headSha is overwritten with the host-reported SHA without the ancestry check recordPush applies.
packages/opencode/src/kilo-sessions/kilo-sessions.ts 525 A migration candidate that already owns a link is never marked pruned, so the { pending } marker can never settle.
Files Reviewed (14 files)
  • packages/opencode/src/cli/cmd/pr.ts
  • packages/opencode/src/kilo-sessions/kilo-sessions.ts - 1 issue
  • packages/opencode/src/kilo-sessions/pr-link-poller.ts - 2 issues
  • packages/opencode/src/kilo-sessions/pr-link.ts - 1 issue
  • packages/opencode/src/kilo-sessions/remote-protocol.ts
  • packages/opencode/src/kilo-sessions/remote-sender.ts
  • packages/opencode/src/kilocode/tool/link-pr.ts
  • packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap
  • packages/opencode/test/cli/pr-status.test.ts
  • packages/opencode/test/kilocode/kilo-sessions.test.ts
  • packages/opencode/test/kilocode/sessions/pr-link-evidence.test.ts
  • packages/opencode/test/kilocode/sessions/pr-link.test.ts
  • packages/opencode/test/kilocode/sessions/remote-sender.test.ts
  • packages/opencode/test/kilocode/tool/link-pr.test.ts

Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@iscekic
iscekic marked this pull request as draft September 29, 2026 07:23
@iscekic iscekic removed the merge-by-human the merge bot routed this PR to a human label Sep 29, 2026
Reject dry-run/delete invocations and empty-source refspecs so a non-push
never advances the session's stored headSha. Leave headSha untouched during
refresh so a force-push by someone else cannot overwrite the session's pushed
evidence, and log a host PR mismatch instead of silently keeping or replacing
the link. Settle a migration candidate that already owns a real link so the
legacy prune marker can complete.
@iscekic

iscekic commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Fixed failing checks in 3eb55b0.

@iscekic
iscekic marked this pull request as ready for review September 29, 2026 09:03
@iscekic iscekic added the merge-by-human the merge bot routed this PR to a human label Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-by-human the merge bot routed this PR to a human

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants