Skip to content

[fix] Refuse an automatic session rename over a name a person typed [AGE-4287] - #6676

Merged
mmabrouk merged 5 commits into
release/v0.115.3from
fix/release-1153-rename-replay
Sep 8, 2026
Merged

[fix] Refuse an automatic session rename over a name a person typed [AGE-4287]#6676
mmabrouk merged 5 commits into
release/v0.115.3from
fix/release-1153-rename-replay

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Sep 8, 2026

Copy link
Copy Markdown
Member

Context

Fixes #6657. With permissions set to Ask, the agent queues a rename_session call and the turn parks on an approval card. You rename the session by hand while the card waits. You approve. The deferred rename then runs with the arguments the agent chose before your rename, your name is gone, and the agent reports the name it wrote.

The agent's own view cannot tell a stale intent from a fresh one. It renamed because it decided to, and by the time the call runs that decision is old. #6667 makes the agent see the session's current name on every new turn, which helps, but a parked approval continues the pending prompt and the queued call already holds its old arguments.

Changes

The row answers the question the agent cannot. A header edit now says where the name came from, and the row remembers whether a person controls it.

PUT/POST /api/sessions/streams/header takes a name_source query parameter:

name_source Who sends it Behavior
manual (default) a person typing a name, and any unmarked caller applies, and stamps the row as person-controlled
automatic the agent's rename_session, and the browser's auto-title refused with 409 when it would change a person's name

The row also counts its name changes. The stamp and the counter are reserved ag. tags, so there is no migration and no client ever reads them. Every existing reserved tag is already stripped from client responses by the same mapper.

The decision is made inside the write transaction, against a row locked with FOR NO KEY UPDATE. A check a layer up would be a read-then-write: a person's rename can commit between the read and the write, and the automatic edit would then replace a name that did not exist when it was checked. Both header writers queue on the row instead, so the loser re-reads the winner's name and is refused on it. The weaker lock mode is deliberate: a session_turns insert takes a KEY SHARE lock on this row for its foreign key, and a rename has no business making a turn wait.

An automatic edit never moves the stamp, and cannot clear the name. Only a manual edit does either. Both matter because the rule lets some automatic edits through on a protected row, and any of them leaving the row less protected than it found it would hand the next stale call an open door.

The refusal carries the current state, in the agent-actionable envelope api/AGENTS.md requires:

HTTP 409
{"detail": {
  "code": "session_name_is_manual",
  "message": "This session is named \"QA-6657 parked rename\", and a person named it.",
  "retryable": false,
  "next_step": "Keep that name and do not rename the session. Only if the person asked you for a different one, send replacing_name and replacing_revision set to the values below.",
  "details": {"current_name": "QA-6657 parked rename", "name_revision": 1}
}}

The runner passes an object detail through to the model, so the agent adopts the name instead of retrying. The message bounds the name it repeats, so a long name cannot push details past the runner's 2000-character cut.

Three things are deliberately not refused. An edit that sets no name only touches the description. An edit whose name already matches the stored one changes nothing, so a retry is safe. And replacing_name with replacing_revision, naming the exact state being replaced, passes while both still match. That is how "rename this session to X" works after you named it yourself.

That pair is a precondition, not a permission, and both halves are load-bearing. A bare "yes, overwrite" flag would be timeless: an agent legitimately authorized to replace A can sit parked while you rename to B, and approving it then overwrites B. A stale call carries that flag as truthfully as a fresh one. The name alone is not enough either: rename to B, restore A, and the same call matches a second time and takes B back. The revision counts every name change and never returns, so an authorization is spent on one state. The name is the half that is not guessable, since revisions count from one.

name_source is a query parameter, never a body field. The rename_session catalog entry fixes name_source=automatic inside its own path and the model fills only the body, so an agent cannot claim a person chose its name. The browser's auto-title sends automatic too, which is what keeps the agent able to name a session over the title derived from your first message.

Why this shape

The brief asked for three options in order.

(a) Refuse an automatic rename over a person's name. Chosen, with (b) folded in. No marker existed, so this PR adds one. A turn-scoped fence was the alternative and it does not work: the reported replay happens on the turn after the approval, whose start is already later than the by-hand rename, so a fence comparing the rename against the turn's start lets it straight through.

(c) Clear a parked rename_session approval when the person renames. Rejected. It covers only the parked case, and the same stale call arrives without an approval whenever a model retries from its own transcript.

Tests

Suite Result
api unit 3302 passed, 176 skipped
api rename-guard integration, real Postgres 8 passed
sdks/python agents unit 1308 passed, 4 skipped
services unit 163 passed
services/runner tool-direct unit 85 passed
web type-check (oss + ee), mobile types:check clean
web @agenta/oss unit 506 passed, 1 skipped
web @agenta/entities, @agenta/sessions-ui unit passed
web lint (oss, entities, sessions-ui) clean

test_stream_rename_guard.py has 46 unit tests over the rule, the stamp, the service and the route, then the sequences that break if any one transition is subtly wrong: repeating a person's name does not open the door, an asked-for rename does not open the door, an authorization cannot be spent twice, and an automatic clear cannot strip the protection.

test_stream_rename_guard_integration.py drives the real DAO against real Postgres. test_a_rename_that_commits_mid_flight_is_not_overwritten holds the row's lock in one transaction, asserts the automatic write waits for it, commits a person's rename, and then asserts the write is refused on the new name. Removing the row lock makes it fail, which I checked.

ruff format and ruff check clean at the CI-pinned 0.15.12. prettier clean.

Live evidence

A standalone EE dev stack built from this branch, harness Pi core, model openai/gpt-4.1-mini through an OpenRouter connection, sandbox local. The journey is the issue's: permissions ask, a gated file write parks the turn and defers the queued call, the person renames by hand while the card waits, the card is approved, then a turn asks the agent what the session is called. Before is the same stack with the catalog path reverted to the branch point, so the only variable is the fix.

Before After
Stored name after the journey file-create-and-shell-command-test QA-6657 parked rename
The agent's answer file-create-and-shell-command-test QA-6657 parked rename
The person's name survived no yes

The runner's own log shows the call and the refusal in the after run:

direct tool call POST http://.../api/sessions/streams/header?session_id=...&name_source=automatic
returned HTTP 409: {"detail":{"code":"session_name_is_manual","message":"This session is named \"QA-6657 parked rename\", and a person named it.", ...}}

Two more cells on the same stack, both green:

  • No by-hand rename. The agent's own rename_session lands and replaces the title derived from the first message, so self-naming is untouched.
  • The person then asks for a different name. The agent gets the refusal, retries with replacing_name and replacing_revision copied from it, and the stored name becomes the asked-for one in the same turn.

What to QA

  • Set an agent's permissions to Ask. Send a message that needs a file write and a shell command. When the card appears, rename the session with Alt+R, then approve. The tab keeps your name.
  • Ask the agent what the session is called. It answers your name.
  • Start a fresh session and send one message. The agent still names the session itself, over the auto-title from your first message.
  • Rename a session by hand, then ask the agent to rename it to something else. It does.
  • Regression: rename a session from the sessions list and from the chat rail. Both still persist across a reload.

Not covered

  • A caller hitting the header endpoint directly, without name_source, is treated as a person. That is the recoverable direction, not a free win: during a rolling deploy a service on an older SDK sends no name_source, so an automatic name it writes is remembered as person-chosen, and the next agent rename of that session is refused until a person renames it. The other default loses a person's name instead.
  • Rows written before this change carry no stamp, so a name a person typed last week is not protected until they rename it again. There is no historical provenance to recover it from.
  • The precondition is still the model's own claim. What the guard checks is that the claim is currently true, not that the person made the request, so a model that copies current_name and name_revision out of the refusal and invents the request gets through. Both values are handed to it precisely so a genuine request can succeed. The tool description states the one case it is for.
  • SessionStreamEdit can still carry name and a whole tags dict, so a future caller of the flag-mirror edit could overwrite the stamp. No live caller sets either field. Narrowing that DTO to the lifecycle fields its callers actually own is worth doing and is not in this PR.
  • A header write on a soft-deleted row returns 200 with a null stream rather than a 404. That silent no-op predates this PR and is unchanged by it.
  • The release gate has no cell that renames a session between turns. #6669 already tracks that gap; the driver behind the table above is written for that shape.

Review rounds

Round 2, ed8cb6186b. Two Major CodeRabbit threads and three Codex P1s. An automatic edit with the same name cleared the stamp, which was deterministic: person names it A, agent submits A, protection disappears, a stale call submits B and succeeds. The guard was not atomic with the write. And the override was timeless. Fixed by making an automatic edit never move the stamp, moving the guard into the write transaction, and replacing override_user_name with a precondition. Codex also asked for the naming to say what it means (author=user|auto became name_source=manual|automatic, since both requests run under the same user credential and only the name is governed), for the storage encoding to leave the core, for the structured error envelope, and for no new field on the public response. All four done.

Round 3, e02f8a8d29. A Codex re-pass found two more, both cases of an allowed automatic write weakening the protection it had just passed. A successful authorized rename could replay after the person restored the earlier name, since the name matched a second time; the revision closes that. And an automatic caller could clear the name, leaving the row with nothing to protect; only a person clears a name now. Also from that pass: FOR NO KEY UPDATE instead of FOR UPDATE, and a bounded name echo in the refusal.

CodeRabbit also asked for a legacy alias mapping author=auto onto the new parameter. Declined, with the evidence in the thread: git log --all -S 'author=auto' returns only this PR's own two commits, so there is no producer anywhere that sends it and the alias would be dead on arrival.

An approval card parks the agent's rename_session call. The person renames the
session by hand while it waits. The deferred call then runs with the name the
agent chose before the rename, so the person's name is replaced and the agent
reports the stale one.

Nothing in the agent's own view separates a stale intent from a fresh one, so the
row answers instead. A header edit now says who chose the name: author=user for a
person typing one, author=auto for a program proposing one. A person's name is
stamped on the row as a reserved ag. tag, and an auto edit that would replace it
is refused with 409 carrying the current name, so the agent adopts it.

author is a query parameter, never a body field. The rename_session catalog entry
fixes author=auto inside its own path and the model fills only the body, so an
agent cannot claim a person chose its name. The browser's auto-title sends
author=auto too, so the agent can still name a session over it.

override_user_name in the body is the way past the refusal, for the rename a
person actually asked for.

Fixes #6657
@linear-code

linear-code Bot commented Sep 8, 2026

Copy link
Copy Markdown

AGE-4287

@mmabrouk

mmabrouk commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
agenta-documentation Ready Ready Preview Sep 8, 2026 9:42pm UTC

Request Review

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📘 Docs preview

Status ✅ Ready
Preview https://pr-6676-agenta-docs-preview.mahmoud-637.workers.dev/docs
Inspect Actions run
Commit 3485718b1ad4cb8813fba098beb4d149bb50ada7

This comment updates in place on every push.

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Warning

Review limit reached

Next included review available in 14 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 8 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: 6545f432-80c1-4036-ae57-d1408e124730

📥 Commits

Reviewing files that changed from the base of the PR and between 952c17e and 3485718.

📒 Files selected for processing (6)
  • api/oss/src/core/sessions/streams/types.py
  • api/oss/tests/pytest/unit/sessions/test_stream_rename_guard.py
  • sdks/python/agenta/sdk/agents/platform/op_catalog.py
  • web/oss/src/components/AgentChatSlice/state/sessions.ts
  • web/packages/agenta-entities/src/session/api/api.ts
  • web/packages/agenta-entities/src/session/api/client.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: b6a94aa4-5b02-4d52-9e0a-570b08d9ff1b

📥 Commits

Reviewing files that changed from the base of the PR and between e02f8a8 and 952c17e.

📒 Files selected for processing (2)
  • api/oss/src/apis/fastapi/sessions/router.py
  • api/oss/tests/pytest/unit/sessions/test_stream_rename_guard.py

Included review availability: Your plan provides up to 8 included reviews per hour; 0 remain after this review.


📝 Summary

Summary by CodeRabbit

  • New Features
    • Session names now distinguish manually chosen titles from automatically generated ones.
    • Automatic renames can use the current name and revision as safeguards before replacing a title.
  • Bug Fixes
    • Automatic renames no longer overwrite manually chosen names or clear titles by default.
    • Conflicts provide the current name, revision, and next-step guidance.
    • Rejected automatic titles are removed from the interface instead of remaining temporarily visible.
  • Documentation
    • Updated rename-session guidance to explain conflict handling and replacement requirements.

Walkthrough

Session names now record their source and revision. Automatic renames require matching replacement data and cannot replace manual names after stale or concurrent updates. The API, clients, persistence layer, catalog, documentation, and tests support this behavior.

Changes

Session rename protection

Layer / File(s) Summary
Rename contracts and protection rules
api/oss/src/core/sessions/streams/dtos.py, api/oss/src/core/sessions/streams/naming.py, api/oss/src/core/sessions/streams/types.py, api/oss/src/core/sessions/streams/interfaces.py
DTOs use SessionNameSource and paired replacing_name/replacing_revision preconditions. The guard distinguishes protected names, stale requests, and automatic clears.
Atomic persistence and metadata mapping
api/oss/src/dbs/postgres/sessions/streams/*, api/oss/src/core/sessions/streams/service.py
Mappings persist name source and revision tags. The DAO locks the session row before evaluating and applying a rename. The service forwards the source through create and update paths.
API, clients, and catalog propagation
api/oss/src/apis/fastapi/sessions/router.py, web/packages/agenta-entities/src/session/api/api.ts, web/oss/src/components/AgentChatSlice/state/sessions.ts, web/packages/agenta-sessions-ui/src/useSessionActions.tsx, sdks/python/agenta/sdk/agents/platform/op_catalog.py, docs/design/agent-workflows/documentation/tools.md, services/runner/tests/unit/tool-direct.test.ts
The API accepts name_source and returns structured 409 responses. Web clients mark manual and automatic names. The platform catalog sends fixed automatic-source requests with replacement fields.
Rename protection validation
api/oss/tests/pytest/unit/sessions/*, sdks/python/oss/tests/pytest/unit/agents/platform/test_op_catalog.py
Tests cover revision stamping, stale and replayed requests, concurrent row locking, route forwarding, direct-call query protection, and metadata preservation.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Severity of issue fixed: Medium

Merge Risk: 🔵 Low · up to 952c1

Session renames now preserve manually chosen names against stale automatic updates and return revision-aware replacement details. The remaining risk is limited to a web comment-format convention and does not affect runtime behavior.

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant SessionStreamsRouter
  participant SessionStreamsService
  participant SessionStreamsDAO
  Agent->>SessionStreamsRouter: rename_session with name_source=automatic
  SessionStreamsRouter->>SessionStreamsService: set_header with replacement fields
  SessionStreamsService->>SessionStreamsDAO: update_header
  SessionStreamsDAO->>SessionStreamsDAO: lock row and evaluate name state
  SessionStreamsRouter-->>Agent: HTTP 409 envelope or successful rename
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.89% which is insufficient. The required threshold is 60.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 108 functions across 18 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: refusing automatic session renames that would overwrite a name entered by a person.
Description check ✅ Passed The description directly explains the stale rename problem, the implementation, affected behavior, tests, and intended safeguards.
Linked Issues check ✅ Passed The implementation addresses issue #6657 by protecting manual names from stale automatic renames, preserving automatic naming when unprotected, providing the current name and revision on refusal, and …
Out of Scope Changes check ✅ Passed The API, database, service, SDK, runner, web, documentation, and test changes all support the linked session-rename protection objective. No unrelated code changes are evident.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/release-1153-rename-replay

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-09-08T21:53:53.249Z

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
web/packages/agenta-entities/src/session/api/api.ts (1)

838-839: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Condense both comments to one short line. web/AGENTS.md applies to both cited files and sets this as a hard rule. Keep only the essential author explanation at each site.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: 8af2974b-4800-475a-a072-1776421ce6df

📥 Commits

Reviewing files that changed from the base of the PR and between f84fa7b and 3bb329c.

📒 Files selected for processing (16)
  • api/oss/src/apis/fastapi/sessions/router.py
  • api/oss/src/core/sessions/streams/dtos.py
  • api/oss/src/core/sessions/streams/interfaces.py
  • api/oss/src/core/sessions/streams/service.py
  • api/oss/src/core/sessions/streams/types.py
  • api/oss/src/dbs/postgres/sessions/streams/dao.py
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py
  • api/oss/tests/pytest/unit/sessions/test_stream_fill_once.py
  • api/oss/tests/pytest/unit/sessions/test_stream_header_merge.py
  • api/oss/tests/pytest/unit/sessions/test_stream_rename_guard.py
  • docs/design/agent-workflows/documentation/tools.md
  • sdks/python/agenta/sdk/agents/platform/op_catalog.py
  • sdks/python/oss/tests/pytest/unit/agents/platform/test_op_catalog.py
  • services/runner/tests/unit/tool-direct.test.ts
  • web/oss/src/components/AgentChatSlice/state/sessions.ts
  • web/packages/agenta-entities/src/session/api/api.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 0 remain after this review.

Comment thread api/oss/src/core/sessions/streams/service.py Outdated
Comment thread api/oss/src/dbs/postgres/sessions/streams/dao.py Outdated
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Already reviewed the last commit. Use @coderabbitai full review to rerun a review of the entire changeset.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…ind the override to a name

Three changes from review.

An automatic edit no longer moves the name-source stamp at all. It used to
clear it on every edit that carried a name, so the agent repeating the name a
person typed - which the rule allows, because it changes nothing - left the row
unprotected and the next automatic rename replaced the person's name.

The guard now runs inside the DAO's write transaction, against a row locked
with FOR UPDATE. A check a layer up was a read-then-write: a person's rename
could commit between the read and the write, and the automatic edit would
replace a name that did not exist when it was checked.

override_user_name is replaced by replacing_name, the exact name the edit
replaces. A bare permission is timeless: an agent authorized to rename A can
sit parked while the person renames to B, and approving it then overwrites B.
A precondition cannot, because it stops matching.

Also from review: author=user|auto is now name_source=manual|automatic, which
names where a name came from rather than who called; the tag encoding moved out
of the core DTOs into the postgres mapper and the rule into its own core module;
the 409 carries the agent-actionable envelope from api/AGENTS.md instead of a
bare string; and SessionStream no longer exposes the decoded source, since only
the write path reads it.

Adds a Postgres integration suite. One cell holds the row's lock and asserts the
automatic write waits for it, which fails without FOR UPDATE.
@mmabrouk

mmabrouk commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
web/packages/agenta-entities/src/session/api/api.ts (1)

810-813: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Shorten the four nameSource comments to one short line each. web/AGENTS.md defines this as an explicit web contract. ESLint and Prettier do not enforce comment length, so this is a maintainability cleanup rather than a CI failure.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: ea4d5b80-06b5-4b83-8176-e6a5ffdeb0c0

📥 Commits

Reviewing files that changed from the base of the PR and between 3bb329c and ed8cb61.

📒 Files selected for processing (19)
  • api/oss/src/apis/fastapi/sessions/router.py
  • api/oss/src/core/sessions/streams/dtos.py
  • api/oss/src/core/sessions/streams/interfaces.py
  • api/oss/src/core/sessions/streams/naming.py
  • api/oss/src/core/sessions/streams/service.py
  • api/oss/src/core/sessions/streams/types.py
  • api/oss/src/dbs/postgres/sessions/streams/dao.py
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py
  • api/oss/tests/pytest/unit/sessions/test_stream_fill_once.py
  • api/oss/tests/pytest/unit/sessions/test_stream_header_merge.py
  • api/oss/tests/pytest/unit/sessions/test_stream_rename_guard.py
  • api/oss/tests/pytest/unit/sessions/test_stream_rename_guard_integration.py
  • docs/design/agent-workflows/documentation/tools.md
  • sdks/python/agenta/sdk/agents/platform/op_catalog.py
  • sdks/python/oss/tests/pytest/unit/agents/platform/test_op_catalog.py
  • services/runner/tests/unit/tool-direct.test.ts
  • web/oss/src/components/AgentChatSlice/state/sessions.ts
  • web/packages/agenta-entities/src/session/api/api.ts
  • web/packages/agenta-sessions-ui/src/useSessionActions.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/design/agent-workflows/documentation/tools.md
  • api/oss/src/dbs/postgres/sessions/streams/dao.py
  • api/oss/src/core/sessions/streams/service.py

Included review availability: Your plan provides up to 8 included reviews per hour; 0 remain after this review.

Comment thread api/oss/src/apis/fastapi/sessions/router.py Outdated
Comment thread web/oss/src/components/AgentChatSlice/state/sessions.ts
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Already reviewed the last commit. Use @coderabbitai full review to rerun a review of the entire changeset.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…tomatic clear

Two more holes from the Codex re-pass, both of which let an automatic write
weaken the protection it had just been checked against.

A successful authorized rename could replay. The person names the session A,
asks the agent for B, gets B, then puts A back themselves. Replaying the same
call took B again, because the name matched a second time. The row now carries
a name revision that counts every name change, and the precondition names both
the name and the revision it was read at. A restored name brings back the
string, never the revision, so the authorization is spent.

Both halves are load-bearing. A revision counts from one and is guessable; a
name is not. A name repeats; a revision does not.

An automatic caller could clear the name. On a person-named session, an
authorized {name: ""} left the row with an empty name, and every later
automatic rename walked past the guard, because a row with no name has nothing
to protect. Only a person removes a name now.

Also: FOR NO KEY UPDATE rather than FOR UPDATE, so a rename does not contend
with the KEY SHARE lock a session_turns insert takes on this row; and the
refusal message bounds the name it repeats, so a long name cannot push the
machine-readable half of the envelope past the runner's 2000-character cut.

Web: the browser auto-title now drops its optimistic title when the server
refuses the write, instead of showing a name the server rejected until the next
list poll disagrees.
A warm agent session holds the tool descriptors it opened with, so a sandbox
started under an earlier build of this change can still execute
?author=auto after the service has moved on. Reading it costs one branch and
keeps that call on the guarded path; the alternative is a rename that silently
claims a person made it.

An explicit name_source always wins, so the alias cannot override a caller that
speaks the current spelling, and an unrecognized value falls through to the
default rather than failing the rename. The parameter is hidden from the schema
and marked deprecated, and can be deleted once no session that old can be alive.

This never shipped in a release: git log --all -S 'author=auto' returns only
this PR's own commits.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: e6189080-8d59-4890-91e9-5a1f3ffc4a31

📥 Commits

Reviewing files that changed from the base of the PR and between ed8cb61 and e02f8a8.

📒 Files selected for processing (10)
  • api/oss/src/core/sessions/streams/dtos.py
  • api/oss/src/core/sessions/streams/naming.py
  • api/oss/src/core/sessions/streams/types.py
  • api/oss/src/dbs/postgres/sessions/streams/dao.py
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py
  • api/oss/tests/pytest/unit/sessions/test_stream_rename_guard.py
  • api/oss/tests/pytest/unit/sessions/test_stream_rename_guard_integration.py
  • sdks/python/agenta/sdk/agents/platform/op_catalog.py
  • sdks/python/oss/tests/pytest/unit/agents/platform/test_op_catalog.py
  • web/oss/src/components/AgentChatSlice/state/sessions.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • api/oss/src/core/sessions/streams/dtos.py
  • web/oss/src/components/AgentChatSlice/state/sessions.ts
  • sdks/python/oss/tests/pytest/unit/agents/platform/test_op_catalog.py

Included review availability: Your plan provides up to 8 included reviews per hour; 0 remain after this review.

Comment thread api/oss/src/core/sessions/streams/types.py
A rename can store a name longer than the prose bound, and replacing_name has
to match the stored value exactly. A caller that copied a shortened quote out
of the message was refused forever with no way to tell why, so a long
person-chosen name became impossible to replace legitimately.

The sentences no longer repeat a name they cannot repeat whole: past the bound
they stop naming the session and point at details.current_name instead. Every
next_step now names details.current_name and details.name_revision rather than
the prose above it. The revision comes first in details, so the runner's
2000-character cut of the serialized detail reaches the small field before a
long name can crowd it out.

Two follow-ups from the Codex pass, both non-blocking there.

replacing_revision now accepts 0. A title filled from a session's first message
carries no revision, so a person who keeps that title owns a name nobody
counted; rejecting 0 made that one session impossible to rename on request.

The browser auto-title clears its optimistic title only when the server REFUSED
the write, not when the write merely failed. A refusal means the server holds a
name a person chose. A network failure means nobody knows yet, and blanking the
row then would lose a title for no reason.
@mmabrouk
mmabrouk merged commit 66ed5a6 into release/v0.115.3 Sep 8, 2026
54 of 55 checks passed
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.

1 participant