Skip to content

feat: stop foreground execute processes when a user interrupts a chat - #27372

Draft
ibetitsmike wants to merge 1 commit into
mike/codagt-757-lite/02-chatd-client-tokensfrom
mike/codagt-757-lite/04-interrupt-kill
Draft

ibetitsmike wants to merge 1 commit into
mike/codagt-757-lite/02-chatd-client-tokensfrom
mike/codagt-757-lite/04-interrupt-kill

Conversation

@ibetitsmike

@ibetitsmike ibetitsmike commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Stack context

Now based on #27370, not #27371. #27371 is parked as a draft while its timeout policy is reworked; this PR never depended on it beyond three prose mentions of "attempt watchdog", which are now "attempt timeout".

Spans agent/, codersdk/, and coderd/x/chatd/, so the title is scopeless.

What

A user action that replaces the current turn stops unresolved foreground work; an internal retry or worker move leaves it running so the next attempt can recover it.

The runner cancels the active task with an explicit cause (chattool.ErrUserInterrupt) when a user interrupt, an edit, or a message sent with busy_behavior=interrupt supersedes it. A foreground execute call unwinding on that cause stops its process on a detached, bounded context. Runner shutdown, rebalancing, and attempt timeouts cancel without that cause, so their processes keep running and a retried attempt re-attaches through its idempotency key instead of running the command twice. Background processes are deliberately spared; they are addressable through their handle and the process list.

Cancelling by identity, not by process ID

Requiring the process ID from StartProcess leaves nothing to cancel when the agent starts the process but the response does not reach chatd.

The stop is armed before the start request rather than after it. When the response did arrive, the process ID is used. When it did not, chatd still knows the idempotency key, and a chat-scoped route resolves that key to the process it started:

POST /api/v0/processes/signal-by-idempotency-key
{"idempotency_key": "...", "signal": "kill"}

The key travels in the body rather than the path. It carries a provider-supplied tool call ID, and chi's path handling makes an opaque path segment fiddly: chi routes on RawPath when it is set and on Path otherwise, so chi.URLParam returns an escaped value in the first case and a decoded one in the second. A key containing a literal % lands in the second case, where url.PathUnescape then fails with invalid URL escape. Keeping the key out of the path removes that class of bug rather than handling it. TestSignalProcessByIdempotencyKey/AcceptsKeysNeedingURLEscaping covers /, %, a space, ?, #, and ...

The stored reservation does the translation, so process details stay behind the operation that owns them. Reservations are chat-scoped after #27369, so another chat's key is simply absent here rather than needing a separate ownership check.

SignalProcessByIdempotencyKey on AgentConn is the client side. Agents that predate the route answer a plain 404, which the SDK reports as ErrProcessKeySignalUnsupported rather than "not found", because such an answer says nothing about the process.

Fix: a pending reservation no longer swallows the interrupt

Review asked what distinguishes a pending reservation from a missing one on this path. Traced end to end, the old code lost the kill:

  1. the registry reported false for both an absent key and a pending one;
  2. the manager mapped both to errProcessNotFound;
  3. the route answered a coded 404;
  4. chatd's isAlreadyGoneError read that code as "the process is gone" and returned without logging.

The interrupt was dropped silently and the command kept running, which is what this PR exists to prevent. The window is not exotic: the reservation is inserted before the spawn, and the by-key path is used precisely when the start response was lost, so a pending reservation is the expected state there.

The registry now exposes Await, which waits out a pending reservation inside the caller's context and distinguishes three outcomes:

Outcome Answer
Published Signal the process.
Not reserved, or released while waiting 404 process_key_not_found. A genuine not-found.
Still pending at the deadline 409 start_pending. chatd logs it instead of treating the command as gone.

Red-green: removing the wait makes TestSignalByKeyWaitsForPendingStart and TestSignalByKeyReportsPendingAtDeadline fail, and restoring it makes them pass. TestExecuteToolInterruptKill/PendingStartIsNotTreatedAsGone covers the chatd side.

Other review changes in this revision

  • interruptStopper is now userInterruptStopper: the old name read as stopping interrupts.
  • The by-key symbols dropped "token" throughout, matching feat: add a run-once reservation mechanism to the workspace agent process API #27369's IdempotencyKey rename.
  • decodeSignalRequest became validateSignal, since the two routes decode different bodies.
  • Added comments were shortened and the "keep running behind a result nobody reads" line in ARCHITECTURE.md was rewritten.

Limitations

The stop stays best-effort. An unreachable agent, or a worker crash before unwinding, leaves the process running; it remains visible and killable through the process list. No state needs to survive the workspace agent's lifetime.

This PR was written and revised by Mux, an AI coding agent, operating on Mike's behalf.

@linear-code

linear-code Bot commented Jul 21, 2026

Copy link
Copy Markdown

CODAGT-757

Copy link
Copy Markdown
Collaborator Author

@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 1bd6649e55

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/04-interrupt-kill branch from 1bd6649 to a45190d Compare July 21, 2026 11:22
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/03-attempt-watchdog branch from 7b7cd6d to 2cc3b31 Compare July 21, 2026 11:22
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: a45190da30

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/04-interrupt-kill branch from a45190d to e1c9876 Compare July 21, 2026 11:43
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/03-attempt-watchdog branch from 2cc3b31 to c717bbb Compare July 21, 2026 11:43
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: e1c98763d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/04-interrupt-kill branch from e1c9876 to 7fc8022 Compare July 21, 2026 11:58
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/03-attempt-watchdog branch from c717bbb to 1191ea5 Compare July 21, 2026 11:58
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 7fc8022bea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/03-attempt-watchdog branch from 1191ea5 to 1e65a02 Compare July 21, 2026 13:07
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/04-interrupt-kill branch from b6a36e4 to 4a10e14 Compare July 29, 2026 12:08
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/03-attempt-watchdog branch from dc2b188 to 7a8997c Compare July 29, 2026 13:56
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/04-interrupt-kill branch from 4a10e14 to 2e24ea9 Compare July 29, 2026 13:56
@ibetitsmike ibetitsmike changed the title feat(coderd/x/chatd): best-effort kill foreground execute processes on user interrupt feat: stop foreground execute processes when a user interrupts a chat Jul 29, 2026
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

Implemented cancel-by-identity here rather than in a follow-up, since merging this as it was would ship exactly the gap you named.

The stop is now armed before the start request instead of after it. When the start response arrived, it signals the process ID as before. When it did not, chatd still knows the idempotency key, and a new route resolves it:

POST /api/v0/processes/tokens/{token}/signal

The stored reservation from #27369 does the translation, so process details stay behind the operation that owns them. SignalProcessByToken is the client side. The route is chat-scoped and answers 404 for an unknown key, a key owned by another chat (rather than disclosing it), and a reservation with no published process yet. Agents that predate the route answer 404 too, which reads as already gone, so they degrade to the previous behavior rather than erroring.

Covered by tests for the lost-response path (signals by key, no process ID involved), an unidentified call with no key (signals nothing), attempt-watchdog cancellation (signals nothing), background processes spared, 404/409 treated as gone, and the cross-chat rejection on the agent side. I verified the lost-response test fails without the fallback.

Explicit background work is untouched, and the stop stays best-effort with no state surviving the agent's lifetime.

One thing this surfaced: with conflicts now typed to the start route, the old helper that treated any 409 as "already gone" no longer matched the signal route's 409. That is a separate helper now, so an exited process is still recognized instead of logging a spurious failure.

Reviewed and revised by Mux, an AI coding agent, operating on Mike's behalf.

@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/03-attempt-watchdog branch from 7a8997c to 7a489fb Compare July 29, 2026 14:33
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/04-interrupt-kill branch from 2e24ea9 to ead9530 Compare July 29, 2026 14:33
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

Follow-up: a self-audit caught a real flaw in how I handled old agents here, now fixed.

The stopper treated any 404 from the by-token signal as proof the process was gone. An agent without the route also answers 404, and that answer says nothing about the process, which could still be running after a user interrupt. My comment claiming it "reads as gone" was simply wrong.

The route now returns a process_token_not_found code, and the SDK distinguishes the two: a coded 404 means no process is held under the key, while any other 404 becomes ErrProcessTokenUnsupported and is logged rather than suppressed. TestExecuteToolInterruptKill/OldAgentUnsupportedRouteIsNotTreatedAsGone asserts the log, and the SDK classification is covered separately.

The limitation is now stated honestly in ARCHITECTURE.md: with a lost start response against an old agent, the process cannot be stopped, and that is reported rather than mistaken for a process that already stopped.

Reviewed and revised by Mux, an AI coding agent, operating on Mike's behalf.

@ibetitsmike
ibetitsmike requested a review from mafredri July 29, 2026 15:48
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/03-attempt-watchdog branch from 7a489fb to d3294e6 Compare July 29, 2026 15:52
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/04-interrupt-kill branch from ead9530 to c3f4d01 Compare July 29, 2026 15:52
Comment thread agent/agentrunonce/registry.go Outdated
// Lookup returns the value published for a key, so callers can act on
// an operation by its identity rather than by whatever handle the
// operation happened to return. A pending reservation reports false:
// it has no value yet.

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.

What is the difference between a pending and non-existent reservation? I mean, under what situations could it be pending, whilst the model is trying to terminate it?

And the error should probably be different between pending/non-existent in that situation, not that I think it can happen.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

You found a real bug, and it is likelier than "not that I think it can happen". Traced end to end, the old code silently swallowed the interrupt:

  1. Lookup returned (zero, false) for both an absent key and a pending reservation;
  2. signalByToken mapped both to errProcessNotFound;
  3. the route answered a coded 404;
  4. chatd's isAlreadyGoneError read that code as "the process is gone" and returned without logging.

So the kill was dropped and the command kept running, which is the exact failure this PR exists to prevent. The window is not exotic either: the reservation is inserted before the spawn, and the by-key path is used precisely when the start response was lost, so a pending reservation is the expected state there, not a rare interleaving.

Fixed, and the error is now different per your second point:

  • Registry.Await waits out a pending reservation on the caller's context, reusing the done channel Reserve already waits on.
  • Published -> signal the process. Not reserved, or released while waiting -> ErrNotReserved -> 404 process_key_not_found, a genuine not-found. Still pending at the deadline -> ErrPublicationPending -> 409 start_pending, which chatd does not classify as gone.

Red-green: removing the wait makes TestSignalByKeyWaitsForPendingStart and TestSignalByKeyReportsPendingAtDeadline fail; restoring it makes them pass. TestExecuteToolInterruptKill/PendingStartIsNotTreatedAsGone covers the chatd classification.

Following that thread found a second one in the same classifier, which I would not have looked for otherwise. The by-key route answered an uncoded 409 for an exited process, the SDK ran every by-key 409 through the start-conflict decoder, and an uncoded conflict is coerced to input_mismatch, so a normal exited process logged a failed kill. There is now a not_running conflict code, and isAlreadyGoneError suppresses only that one. KillByKeyTreatsExitedProcessAsGone fails if the code check is reverted to the old blanket conflict rejection.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread coderd/x/chatd/chattool/execute.go Outdated
processWaitRetryDelay = time.Second

// killSignalTimeout bounds the detached best-effort kill sent
// when a user interrupt unwinds a foreground execute call.

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.

Unwind your comments, please.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Deleted. The constant name and its value say everything the comment did.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread coderd/x/chatd/chattool/execute.go Outdated
// process only on this cause: every other cancellation (attempt
// watchdog timeout, worker shutdown, runner rebalancing) leaves the
// process running so a replay can re-attach through its idempotency
// token.

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.

Too long.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Cut from seven lines to three:

ErrUserInterrupt is the cancellation cause chatd uses when a user action supersedes the running turn. Foreground execute kills its process only for this cause.

The enumeration of every other cancellation source went with it; that belongs in the architecture doc, where it already is.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread coderd/x/chatd/chattool/execute.go Outdated
}
}

// interruptStopper kills a foreground process when a user action

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 naming feels bad. Turn gets interrupted so it stops the process, but this reads like it stops interrupts.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed, it read backwards. Renamed to userInterruptStopper: it stops the process, and it does so only on a user interrupt.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread coderd/x/chatd/chattool/execute.go Outdated
logger slog.Logger
clientToken string
processID string
disarmed bool

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.

Consider using a mutex, or are you certain parallel read/writes are impossible?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I am fairly confident parallel access is impossible here, so I would rather not add a mutex that suggests otherwise. All four call sites are in executeForeground on the one goroutine running the tool call:

  • defer func() { stopper.stop(ctx) }() immediately after construction;
  • stopper.disarm() on the start-conflict path;
  • stopper.observeStart(resp.ID) after a successful start;
  • stopper.disarm() after the wait resolves.

Context cancellation touches no stopper field: stop only reads context.Cause(ctx), and it runs in the deferred call on the same goroutine, after every other method has returned. Nothing hands the stopper to another goroutine.

That said, the long comment asserting single-goroutine access is what prompted your question, so I shortened it rather than deleting it. If you would still prefer the mutex as a guard against a future caller, say so and I will add it.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread coderd/x/chatd/ARCHITECTURE.md Outdated

### Interrupt kill

When a user action supersedes a running turn (an interrupt, an edit, or a message sent with `busy_behavior=interrupt`), the runner cancels the active task with the `chattool.ErrUserInterrupt` cause. A foreground `execute` call unwinding on that cause sends a best-effort kill signal on a detached, bounded context: the user asked for the work to stop, and the command would otherwise keep running behind a result nobody reads. A 404 or 409 answer means the process is already gone. Background processes are deliberately spared; they are addressable through their handle and the process list.

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.

"keep running behind a result nobody reads".

Rewrite everything!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Rewritten:

the command would otherwise keep running after the turn result is discarded

The surrounding section changed materially as well, since the by-key route moved to a request body and now waits out a pending reservation instead of reporting it as not-found.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread codersdk/workspacesdk/agentconn.go Outdated
DeleteDevcontainer(ctx context.Context, devcontainerID string) error
RecreateDevcontainer(ctx context.Context, devcontainerID string) (codersdk.Response, error)
SignalProcess(ctx context.Context, id string, signal string) error
SignalProcessByToken(ctx context.Context, clientToken string, signal string) error

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.

Be sure to update clientToken when/if renamed throughout the stack.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in the same revision. ClientToken is IdempotencyKey everywhere in the stack, including the wire tag idempotency_key, SignalProcessByIdempotencyKey on AgentConn, and the route, which is now POST /api/v0/processes/signal-by-idempotency-key with the key in the body.

git grep -i clienttoken and git grep client_token are both empty across the stack.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/04-interrupt-kill branch from c3f4d01 to 7c5cfc1 Compare August 1, 2026 03:27
@ibetitsmike
ibetitsmike changed the base branch from mike/codagt-757-lite/03-attempt-watchdog to mike/codagt-757-lite/02-chatd-client-tokens August 1, 2026 03:27
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/02-chatd-client-tokens branch from 202fa50 to a3f2866 Compare August 1, 2026 04:01
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/04-interrupt-kill branch from 7c5cfc1 to 6a84991 Compare August 1, 2026 04:01
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

Revised, and re-parented onto #27370.

Re-parenting

This PR no longer sits on #27371. You rejected that PR's premise twice, so it should not gate the two run-once PRs below it. The dependency turned out to be prose only: three mentions of "attempt watchdog", now "attempt timeout". #27371 is parked as a draft and its six items are answered there.

Your pending-reservation question was a live bug

registry.Lookup returned false for both an absent key and a pending one, the manager mapped both to errProcessNotFound, the route answered a coded 404, and chatd's isAlreadyGoneError read that code as "the process is gone" and returned without logging. The interrupt was dropped and the command kept running.

The window is not exotic: the reservation is inserted before the spawn, and the by-key path is used precisely when the start response was lost, so a pending reservation is the expected state there. Registry.Await now waits it out inside the caller's context and the three outcomes answer differently, per your second point:

Outcome Answer
Published Signal the process.
Not reserved, or released while waiting 404 process_key_not_found
Still pending at the deadline 409 start_pending, which chatd logs rather than treating as gone

Pulling that thread found a second bug in the same classifier: the by-key route answered an uncoded 409 for an exited process, the SDK ran every by-key 409 through the start-conflict decoder, and an uncoded conflict is coerced to input_mismatch. So a perfectly normal exited process logged a failed kill. There is now a not_running code, and only that one suppresses the kill.

Both fixes are red-green: reverting either makes its test fail and nothing else.

Route change

POST /api/v0/processes/signal-by-idempotency-key with the key in the body, replacing the path parameter. #27370's key is no longer a hash, so it can carry a provider tool call ID with characters that need escaping, and chi's path handling makes that fiddly enough to be worth avoiding. Reasoning is in the description; AcceptsKeysNeedingURLEscaping covers /, %, a space, ?, #, and ...

Other items

interruptStopper -> userInterruptStopper, "token" gone throughout, the killSignalTimeout and ErrUserInterrupt comments cut, and the ARCHITECTURE.md line rewritten. That doc section also had a factually wrong claim that any 404 or 409 means the process is gone, which the fixes above disprove; it now has the table.

One pushback

I did not add the mutex on the stopper. All four call sites are in executeForeground on one goroutine and context cancellation touches no stopper field, so a mutex would suggest a hazard that is not there. I shortened the comment that prompted the question instead. Reasoning in the thread; say the word if you would still prefer the guard.

Revised by Mux, an AI coding agent, operating on Mike's behalf.

@github-actions github-actions Bot added the stale This issue is like stale bread. label Aug 9, 2026
@github-actions github-actions Bot closed this Aug 12, 2026
@ibetitsmike ibetitsmike reopened this Aug 27, 2026
A user action that replaces the current turn should stop unresolved
foreground work, while an internal retry or worker move should leave it
running so the next attempt can recover it.

The runner now cancels the active task with an explicit cause
(chattool.ErrUserInterrupt) when a user interrupt, an edit, or a
message sent with busy_behavior=interrupt supersedes it. A foreground
execute call unwinding on that cause stops its process on a detached,
bounded context. Runner shutdown and rebalancing cancel without the
cause, so their processes keep running and a retried attempt
re-attaches instead of running the command twice. Background processes
are deliberately spared.

The stop is armed before the start request rather than after it, which
closes the case where the agent started the process but the response
never reached chatd. There is no process ID to signal then, but the
idempotency key is still known, so the agent resolves the key to the
process it started through a new by-token signal route. The route is
chat-scoped and answers 404 for a foreign or unknown key rather than
disclosing it.

The stop stays best-effort: an unreachable agent or a worker crash
before unwinding leaves the process running, still visible and
killable through the process list.
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/04-interrupt-kill branch from 6a84991 to a8c96d4 Compare August 27, 2026 14:41
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/02-chatd-client-tokens branch from a3f2866 to 0599aa1 Compare August 27, 2026 14:41
@ibetitsmike
ibetitsmike requested a review from mafredri August 27, 2026 14:44
@github-actions github-actions Bot removed the stale This issue is like stale bread. label Aug 28, 2026
@github-actions github-actions Bot added the stale This issue is like stale bread. label Sep 5, 2026
@github-actions github-actions Bot closed this Sep 8, 2026
@mafredri mafredri reopened this Sep 9, 2026
@github-actions github-actions Bot removed the stale This issue is like stale bread. label Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants