Skip to content

fix(installer): give the agent its rules, unbreak non-JS installs, and run evals through the real installer - #266

Merged
nicknisi merged 11 commits into
mainfrom
feat/installer-agent-efficiency
Oct 21, 2026
Merged

nicknisi merged 11 commits into
mainfrom
feat/installer-agent-efficiency

Conversation

@nicknisi

@nicknisi nicknisi commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Why

After #265, the Opus 5.5 installer passed every scenario but was slow and used many tool calls. The transcripts showed structural problems in the installer, not just in the skills. The settings-isolation fix from this PR shipped separately in #267; this PR is rebased on it.

What

fix(installer): Kotlin, PHP, and Laravel installs aborted before the agent ran. runAgentInstaller read package.json for every integration, and a missing file exits the process ("Could not find package.json… Installer setup canceled."). Vanilla JS checked for a package named workos, then asked "Vanilla JavaScript does not seem to be installed. Do you still want to continue?" (default No), which fails outright in CI and agent mode.

fix(installer): give the agent Claude Code's system prompt and state its rules.

  • The system prompt. When none is set, the SDK sends an empty system prompt. The agent got no tool guidance and discovered the Bash allowlist one denial at a time: about 80% of Bash calls were denied.
  • What it now says:
    • the allowlist, and to use Read/Grep/Glob for inspection
    • no network, workos CLI, or dev servers
    • the installer handles dashboard settings
    • whether this run gets post-run build feedback, which only runners with validation do
  • Allowlist additions:
    • read-only git
    • cd <working dir> && <allowed>
    • per-tool syntax checks: py_compile, ruby -c, php -l
    • dependency changes: remove, go get, composer require
  • Review fix (security): shell operators are now checked on the raw command before a cd prefix is stripped, and cd targets must be plain paths. Before this, cd "/proj/$(id)/.." && git status passed.

fix(installer): treat a timed-out build or typecheck as inconclusive. The post-run check killed builds at 60s and sent the agent a bare "Build failed". On a busy machine, agents then guessed at code changes. A killed check now logs a warning and doesn't trigger a correction. Limits are now 60s (typecheck) and 180s (build).

test(evals): the eval runs the real installer integration. Before, it used its own prompt, which named a skill that no longer exists, and called runAgent directly. Now:

  • It calls each integration's run() after configureInstallEnvironment, the same env step the installer runs.
  • An installer failure fails the scenario.
  • Each scenario writes its own installer log, via runWithLogFile in debug.ts.
  • Four Next.js fixtures moved from 14.2 to 15.3, the installer's minimum.

chore(deps): @workos/skills 0.8.1.

Results

50 scenarios: all JS frameworks plus Python, Ruby, Go, and Elixir. They run through the real installer flow with production tool gating, settings isolation (#267), and the current skills main (0.8.2 content). "Before" is the #265 run.

Opus 4.5 before → after Opus 5.5 before → after
Passed 50/50 → 50/50 50/50 → 50/50
First-attempt pass 72% → 90% 84% → 94%
Scenario duration p50 179s → 126s 364s → 123s
Scenario duration p95 380s → 235s 534s → 260s
Tool calls 1,654 → 1,145 2,824 → 1,475
Wall time (concurrency 8) 25m → 16m 47m → 15m
TTFT p50 12.3s → 5.0s 13.0s → 23.9s

Opus 5.5 leads on first-attempt passes and matches 4.5 on typical install time. Its time to first token (about 24s against 5s) and its slowest installs are still higher.

PHP, Laravel, and Kotlin weren't eval'd because their toolchains aren't installed locally; their abort fix is covered by unit tests. This is one run per model.

Verification

bun run test: 3,490 passed. Typecheck, lint, format:check, and build are clean.

Follow-up

Bump @workos/skills to 0.8.2 (workos/skills#56 backend call shapes) once workos/skills#57 is released.

@greptile-apps

greptile-apps Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The latest changes appear safe to merge.

Summary

The latest change closes the reported Git helper bypass.

  • Kotlin, PHP, and Laravel installs reach the agent with credentials in .env.
  • The installer agent starts with Claude Code guidance and WorkOS rules.
  • Installer agents can run more project checks while shell commands stay constrained.
  • Timed-out builds and typechecks no longer trigger guessed code fixes.
  • Eval scenarios now run through the installer's real integration flow.

No new actionable issues were found. The earlier threads are unnumbered, so they have no numbered finding entries.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Agent requests a Git command] --> B[Check command and arguments]
  B --> C{Can it use diff helpers?}
  C -->|No| D[Allow read-only command]
  C -->|Yes| E{Can protective flags be added?}
  E -->|Yes| F[Allow rewritten command]
  E -->|No| G[Deny and explain supported form]
Loading

Reviews (5) · Last reviewed commit: "fix(installer): deny read-only git diff/..." · Reviewed by Greptile

Comment thread src/lib/agent-interface.ts
Comment thread tests/evals/agent-executor.ts
Comment thread src/lib/agent-interface.ts Outdated
Comment thread tests/evals/agent-executor.ts
Comment thread tests/evals/agent-executor.ts Outdated
…missing package.json

runAgentInstaller read package.json for every integration, and a missing
file exits the process. Kotlin, PHP, and Laravel go through this runner,
so their installs stopped right after detection with "Could not find
package.json". Only JavaScript integrations read it now.

Vanilla JS checked for a package named "workos", which no plain project
has, so it asked "Vanilla JavaScript does not seem to be installed. Do you
still want to continue?" (default No) and failed outright in CI and
agent mode. It is the fallback for any JS project, so the check is
skipped.
…its rules

- systemPrompt: Claude Code preset plus installer rules. Omitted, the SDK
  sends an empty system prompt, so the agent never got Claude Code's tool
  guidance (Read/Grep over cat/grep) and learned the Bash allowlist one
  denied command at a time (about 80% of Bash calls were denied).
- Allowlist: read-only git (status/diff/log/show/ls-files/rev-parse),
  `cd <working dir> && <allowed>`, per-tool syntax checks (python -m
  py_compile/venv, ruby -c, php -l, uv venv), and dependency changes
  (remove/uninstall, go get, go mod tidy, composer require). Flags stay
  per tool because the same flag can run code elsewhere (python -c).
- Non-Next.js JS prompts say the setup reference covers what the
  installer handles, so agents stop hunting for it.
- options.onAgentMessage lets evals observe the real integration run.
…xtures

The eval sent its own prompt ("invoke the workos-authkit-<framework>
skill", a skill that no longer exists) and called runAgent directly, so
it measured a different flow from the installer: agents searched for the
skill, read reference files, and skipped production's validator. It now
calls the integration's run() with installer options, observing messages
through options.onAgentMessage and counting agent:retry corrections. The
opt-in Fizzy fixture keeps its isolated-environment path.

That surfaced two gaps the old path hid: four Next.js fixtures were on
14.2, below the installer's 15.3 minimum (now ^15.3 with React 19;
partial-install moves authkit-nextjs from 0.15 to ^4), and the
Kotlin/PHP/Laravel and vanilla-js aborts fixed in the previous commits.

Eval runs set WORKOS_TELEMETRY=false.
Brings workos/skills#55: the Next.js reference recommends authkitProxy and
warns that a bare @workos-inc/node install pulls v11, outside the
authkit-nextjs ^9 || ^10 peer range.
…nner performs

- The Bash gate stripped `cd <working dir> &&` by resolving the path, then
  checked only the remainder. `cd "/proj/$(id)/.." && git status` resolved
  to /proj and passed, while Bash still ran `$(id)`. Shell operators are
  now checked on the raw command first, and a cd target must be a plain
  path (no $, backticks, globs, quotes, or spaces) to be stripped.
- The system prompt promised that the installer builds the project and
  returns errors. Only runners with validation do (runAgentInstaller);
  Python, Ruby, Go, Elixir, and .NET don't. The prompt now says which,
  based on whether this run has a retry config.
- The setup-reference note no longer claims the installer runs browser
  checks; agents report sign-in flows as untested.
initLogFile() set one module-level path, so concurrent installer runs in
one process (evals) appended to whichever file was selected last.
runWithLogFile(path, fn) gives a run its own log via AsyncLocalStorage;
single runs keep the session log as before.
…staller env

- A blocking installer error (e.g. a Next.js result without /sign-in)
  made AgentExecutor return success: false, but ParallelRunner decided
  pass/fail from the grader alone. Installer failures now fail the
  scenario, with an "Installer completed" check and the error.
- For JS frameworks the eval now calls configureInstallEnvironment, the
  same step the installer runs before the integration, so .env.local
  gets the framework's redirect URI key (NEXT_PUBLIC_WORKOS_REDIRECT_URI
  for Next.js) and client-side prefixed copies.
- Each scenario writes its installer log to
  tests/eval-results/installer-logs/ instead of a shared file.
The post-run quick check gave the build 60s, then killed it and sent the
agent a bare "Build failed" with no output. On a busy machine a Next.js
build passes 60s, so agents got a correction they could not act on and
guessed at changes (in evals, switching middleware to the Node runtime).
A check killed at its limit now logs a warning and does not trigger a
correction. Limits are raised to 60s (typecheck) and 180s (build).
@nicknisi
nicknisi force-pushed the feat/installer-agent-efficiency branch from 6840966 to 03ce737 Compare October 21, 2026 20:27
@nicknisi nicknisi changed the title fix(installer): isolate the agent, state its rules, and unbreak non-JS installs fix(installer): give the agent its rules, unbreak non-JS installs, and run evals through the real installer Oct 21, 2026
@nicknisi

Copy link
Copy Markdown
Member Author

@greptileai review

Comment thread src/lib/agent-interface.ts Outdated
Comment thread src/lib/agent-runner.ts
The read-only git check tested whitespace-split tokens, but the shell
removes quotes and backslashes before git sees them: `git diff
"--output=/tmp/x"` passed and wrote the file, and a quoted --ext-diff
slipped past its ban. Git commands containing quotes or backslashes are
now denied, so the tokens match what runs. Allowed diff/show/log commands
also run with --no-ext-diff --no-textconv, so no configured external diff
or textconv program executes.
With the package.json abort gone, these installs reached the agent with
no credentials in the project: the installer's environment step skips
non-JS apps, and runAgentInstaller skipped its own write when the caller
supplied credentials. Non-JS integrations on this runner now always get
WORKOS_API_KEY, WORKOS_CLIENT_ID, and WORKOS_REDIRECT_URI in .env (not
.env.local; Laravel has a package.json for Vite), and the prompt names
.env and no longer claims a generated cookie password.
@nicknisi

Copy link
Copy Markdown
Member Author

@greptileai review

@nicknisi

Copy link
Copy Markdown
Member Author

@greptileai review — the quoted-flag bypass is fixed in 3f7cfa2: git commands containing quotes or backslashes are denied, and allowed diff/show/log run with --no-ext-diff --no-textconv.

Comment thread src/lib/agent-interface.ts
…e disabled

The read-only git check reads the command after removing `2>&1`, but the
--no-ext-diff/--no-textconv rewrite matched the original text, so `git
2>&1 diff` was allowed unrewritten. The rewrite also matched the first
"git diff|show|log" anywhere, so `git 2>&1 diff git log` rewrote the
trailing path arguments instead of the real call. The rewrite is now
anchored to the start of the command, and diff/show/log are denied when
it can't apply, with a message saying to put options after the
subcommand.
@nicknisi

Copy link
Copy Markdown
Member Author

@greptileai review

@nicknisi
nicknisi merged commit 8b223d6 into main Oct 21, 2026
7 checks passed
@nicknisi
nicknisi deleted the feat/installer-agent-efficiency branch October 21, 2026 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant