Skip to content

Launch fixes: Slack robustness, env handling, CI, docs, and cleanup - #16

Merged
jacobprall merged 11 commits into
mainfrom
launch-fixes
Oct 18, 2026
Merged

jacobprall merged 11 commits into
mainfrom
launch-fixes

Conversation

@jacobprall

@jacobprall jacobprall commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Completes the 11 agent-only launch issues for codebase-researcher, one commit per issue.

What changes for users

  • Slack is sturdier. The bot acknowledges each event before it reads the thread or starts the run, so a long thread can no longer miss Slack's 3-second deadline. Threads over the limits (200 messages, 8,000 characters each) are trimmed instead of failing silently. When a run can't start, the thread gets a failure reply and a ⚠️ instead of a stuck 👀. Mentions from channels that aren't allowed, and a Slack setup with no allowed channels at all, are now logged.
  • Invalid input gets an answer. answer_question turns any invalid input into a failure reply and still delivers it to the callback.
  • Blank env values mean "unset" everywhere. A sync: false variable left empty in the Dashboard no longer crashes the API or the sandbox config at boot.
  • Snapshot errors say what's wrong. "No snapshot" (never built, or the cron has been failing) and "not ready" (being captured, or the last capture failed) now get different messages.
  • The non-dumpable runner is enforced. A restored sandbox where verify.sh couldn't prove the runner is non-dumpable is refused, so the README's claim holds on every investigation.

Breaking changes (before launch, on purpose)

  • Callback headers are now X-Researcher-Timestamp and X-Researcher-Signature (were X-Triage-*), and Slack reply metadata is researcher_reply.
  • The snapshot layout moved from /opt/triage to /opt/researcher (RESEARCHER_* in the sandbox env file). After deploying, trigger codebase-researcher-snapshots; until a new snapshot exists, investigations fail verification.

Docs and repo

  • New docs/channels.md: how to add a channel beyond Slack, with a type-checked Discord slash-command sketch.
  • README: Troubleshooting and Operating limits sections, .env.example loading, the live smoke test, new log events, and trn- run IDs.
  • docs/design.md: two more residual risks (anyone who can ask can read the corpus; the allow-listed model host is shared) and what verification guarantees.
  • hello-sandbox.ts uses the current { type } network-policy shape.
  • CI: type-check and tests on every PR. Blueprint validation runs when the repo has a RENDER_API_KEY secret and a RENDER_WORKSPACE_ID variable (it needs a real workspace), and is skipped otherwise.
  • The flaky runner test is fixed. The cause: Pi stops reading a command's stdout 100 ms after the process exits, so under load the env output could come back empty. The tests that inspect a command's environment now write it to a file.
  • Cleanup: dead code, a shared repo schema, prompt_version and repos in turn_finished, build filters that include .node-version.

Verification

pnpm typecheck, pnpm test (239 passed, 1 skipped live test), shellcheck on sandbox-runtime/, and render blueprints validate render.yaml all pass locally; CI passes on Linux. Not verified against a live deployment: the Slack flow end to end, and a snapshot build with the renamed layout.

Closes #3
Closes #4
Closes #5
Closes #6
Closes #7
Closes #9
Closes #10
Closes #11
Closes #12
Closes #13
Closes #14

jacobprall and others added 11 commits October 17, 2026 16:48
… name

Callback receivers verify X-Researcher-Timestamp and X-Researcher-Signature
instead of the X-Triage-* names left from the project's first name, and
Slack replies are tagged researcher_reply. Renaming after launch would break
every receiver.

Closes #3

Co-authored-by: Cursor <cursoragent@cursor.com>
… is invalid

A thread with a pasted log over 8,000 characters, or more than 200 messages,
failed validation in answer_question before any reply was sent, so the eyes
reaction stayed and nobody heard back. The Slack adapter now keeps the
opening message and the most recent ones and cuts long messages, telling the
agent what it left out. answer_question also turns any invalid input into a
failure reply and still delivers it to the caller's callback.

Closes #4

Co-authored-by: Cursor <cursoragent@cursor.com>
A sync: false variable left empty in the Dashboard arrives as "". The
workflow already dropped blank values, but the API, the Render sandbox config,
and the local sandbox config didn't, so a blank optional value crashed them
at boot (a blank RENDER_WORKSPACE_ID failed its tea- check, for one). They
all use one present() helper now.

Closes #5

Co-authored-by: Cursor <cursoragent@cursor.com>
The example sent { default: "deny-all" }, the shape the API replaced with
{ type, rules }, and its comment suggested allow-listing hosts. It now sends
{ type: "deny-all" } the way wirePolicy does, and docs/sandboxes.md says
which SDK workarounds the example actually contains.

Closes #6

Co-authored-by: Cursor <cursoragent@cursor.com>
CI type-checks and runs the tests on every pull request and push to main. A
second job validates render.yaml with the Render CLI when the repository has
a RENDER_API_KEY secret and a RENDER_WORKSPACE_ID variable, since validation
checks plans, regions, and conflicts against a real workspace.

The flaky test in test/runner.test.ts read env's output from the bash tool
result. Pi stops reading a command's stdout 100 ms after the process exits,
so under load that result could be empty. The two tests that inspect a
command's environment now have it write to a file in scratch and read that.

Closes #7

Co-authored-by: Cursor <cursoragent@cursor.com>
The handler added the reaction, called auth.test, paged through the thread,
and started the workflow before responding, and only the start had a
timeout, so a long thread or a slow API could miss Slack's 3-second
deadline and trigger retries. It now responds 200 right away and does that
work afterwards. Retries (X-Slack-Retry-Num) are logged and handled like the
original delivery: the run is keyed on the event id, so a retry starts no
second run, and a retry after an outage still gets answered. When the run
can't be started, the thread gets a failure reply and a warning reaction
instead of a 500.

Closes #9

Co-authored-by: Cursor <cursoragent@cursor.com>
…ions

An empty ALLOWED_CHANNEL_IDS became an empty set, and every mention was
dropped without a log line, so a new deployment looked like a bot that never
answers. The adapter now logs slack_no_channels when it mounts with no
allowed channels, and mention_ignored with the channel for each mention it
drops.

Closes #10

Co-authored-by: Cursor <cursoragent@cursor.com>
docs/channels.md describes the four steps every adapter takes (acknowledge,
build a transcript, start a question with a callback, verify and render the
reply), where an adapter can run, how to render model output safely, and a
type-checked sketch of a Discord slash command. The README and design doc
link to it.

Closes #11

Co-authored-by: Cursor <cursoragent@cursor.com>
…shot is missing

The README now walks through the failures a new deployment hits (allow-list
rejected, a key that sees several workspaces, no snapshot, a silent Slack
bot, a missing reply) and documents the platform limits that bound a
deployment: the shared Sandbox API rate limit, sandbox size, region, 30-day
run retention, snapshot expiry, and worst-case timeouts.

A missing snapshot used to always say the first build takes about 5 minutes,
even when the cron had been failing for days. Now a snapshot that exists but
isn't available says a build is running or failed, and no snapshot at all
says the cron may be failing and an operator should check it.

Closes #12

Co-authored-by: Cursor <cursoragent@cursor.com>
…ve the runner is non-dumpable

The design doc now covers that anyone who can ask can read the whole corpus,
and that the allow-listed model host is shared by all of the provider's
customers, so a manipulated agent could send code there under another key.

verify.sh can only check that the runner is non-dumpable when it runs as
root, and the result was only logged. openRenderSandbox now refuses a
sandbox where the check didn't pass, so the README's claim holds on every
investigation, and the design doc says what verification guarantees.

Closes #13

Co-authored-by: Cursor <cursoragent@cursor.com>
- Remove the unused DEFAULT_MODEL, and move manifestDigest (only a test used
  it) into that test. RepoProfile and BotConfig share one repo schema.
- Log prompt_version and repos in turn_finished; they were computed and
  dropped.
- .env.example lists every variable the code reads, drops the fake Slack
  server URL, and says how to load it; the README says the same.
- Rename the internal triage names left from the project's first name:
  RESEARCHER_* in the sandbox env file, /opt/researcher, and
  /tmp/researcher-stage. This changes the snapshot layout, so trigger the
  snapshot cron after deploying.
- Add .node-version, pnpm-workspace.yaml, and (for the API) tsconfig.json to
  the build filters, so a Node version bump redeploys.
- Use the trn- run ID prefix in the README examples.

Closes #14

Co-authored-by: Cursor <cursoragent@cursor.com>
@jacobprall
jacobprall marked this pull request as ready for review October 17, 2026 23:55
@jacobprall
jacobprall merged commit f4914e2 into main Oct 18, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment