Skip to content

Resolve PR #36 review feedback: untrack dist/ and sync with upstream main#1

Merged
rlweb merged 49 commits into
cache-pluginfrom
claude/nice-bohr-a485n8
Jun 15, 2026
Merged

Resolve PR #36 review feedback: untrack dist/ and sync with upstream main#1
rlweb merged 49 commits into
cache-pluginfrom
claude/nice-bohr-a485n8

Conversation

@rlweb

@rlweb rlweb commented Jun 15, 2026

Copy link
Copy Markdown
Member

What

Addresses the two requests from maintainer @panda-sandeep on bug0inc/passmark#36 (the "Pluggable cache with redis as fallback" PR, whose head is sparklayer-io:cache-plugin):

  1. "Could you uncommit the dist/ directory?"dist/ was force-committed despite being listed in .gitignore. This branch removes it from tracking (git rm -r --cached dist); it stays ignored.
  2. "If you can resolve the merge conflict, that would be great!"cache-plugin had diverged significantly from bug0inc/passmark:main. This branch merges current upstream main and resolves all conflicts.

How the merge conflicts were resolved

Upstream had meanwhile refactored src/redis.ts from a module-level singleton into a lazy, config-aware getRedis() (honouring configure({ redis: { url } }) then REDIS_URL). The cache-plugin feature had instead replaced redis.ts with an eagerly-constructed pluggable cache store.

Rather than pick one design, the resolution keeps upstream's richer redis.ts and reworks src/cache.ts into a pluggable layer that wraps it:

  • getCache() is now lazy + memoized (mirrors getRedis()), so configure() still works before first use.
  • CACHE_PROVIDER selects the backend: redis (default), file (JSON on disk at CACHE_DIR, handy for committing a warm cache to Git for CI — the original use case), or none.
  • The redis backend delegates to getRedis(), so the upstream configure({ redis }) API and REDIS_URL fallback are preserved.
  • data-cache.ts and index.ts now call getCache(); tests mock ../cache's getCache.

Also fixed a duplicate createOpenAI import in src/models.ts introduced by the auto-merge, and documented CACHE_PROVIDER/CACHE_DIR in .env.example and the README.

Verification

  • pnpm run build (tsc) — passes
  • pnpm test164 passed
  • pnpm run lint — 0 errors (pre-existing any warnings only)

Note on branches

This PR targets cache-plugin so the diff shows exactly the dist removal + upstream sync. To actually update bug0inc#36, these commits need to land on the cache-plugin branch (e.g. merge this PR, or I can push directly to cache-plugin if you'd prefer — just say the word).

https://claude.ai/code/session_01WvniLbWm19ZpfXxKRyW6Kv


Generated by Claude Code

aayushpurplesectors and others added 30 commits April 16, 2026 02:06
Adds `opencodezen` as a new AI gateway option alongside the existing
`vercel`, `openrouter`, and `cloudflare` options. Uses @ai-sdk/openai
with baseURL https://opencode.ai/zen/v1 (OpenAI-compatible endpoint).
Includes model ID aliasing to map canonical provider/model IDs to
Zen's naming convention (e.g. anthropic/claude-haiku-4.5 → claude-haiku-4-5).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Override tar to ^7.5.0 to ensure latest patched version
- Fixes CVE-2026-23745, CVE-2026-24842, CVE-2026-26960, CVE-2026-29786, CVE-2026-31802
- Resolves hardlink/symlink path traversal vulnerabilities in node-tar
feat(cua): add CUA mode via OpenAI Responses API
feat: support per-step and per-call AI overrides in runSteps/runUserFlow
fix: patch tar vulnerabilities via npm overrides
Previously the browser_upload_file tool had a schema/implementation
mismatch: the schema claimed to accept absolute paths but the
implementation always prefixed with uploadBasePath, causing absolute
paths like /tmp/file.png to become invalid (./uploads//tmp/file.png).

- Extract resolveUploadPath() helper that detects absolute paths
  (Unix / or Windows C:\) and uses them as-is
- Update Zod schema description to document both absolute/relative
  path support
- Remove misleading inline comment
- Add unit tests for all path resolution cases

Closes bug0inc#45
Ipseeta and others added 19 commits May 12, 2026 17:38
 Add video assertions: record step run and validate via Gemini Files API
…-models

feat: add support for openai models
…-run-step

Sam/extend callback url to run step
…le-path-consistency

fix: support both absolute and relative paths in browser_upload_file
Add consensusPolicy: fail-on-disagreement option for assertions
The dist/ build output is already listed in .gitignore and should not be
tracked. Per maintainer review feedback on PR bug0inc#36, untrack it.
…85n8

# Conflicts:
#	README.md
#	src/__tests__/data-cache.test.ts
#	src/__tests__/integration/run-steps.test.ts
#	src/data-cache.ts
#	src/index.ts
#	src/redis.ts

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces CUA (Computer-Use Agent) mode for visual, coordinate-based browser automation via OpenAI's Responses API, video-based assertions evaluated via Gemini's Files API, support for the OpenCode Zen gateway, and configurable consensus policies for assertion disagreements. It also refactors Redis and Cache initialization to be lazy. The review feedback highlights several critical issues: potential TypeError crashes due to unsafe property access on test?.info() in src/assertion.ts and missing null checks on action in src/cua/actions.ts; a compatibility issue with screencast in src/video.ts on non-Chromium browsers; and configuration-sync bugs in src/redis.ts and src/cache.ts where static initialization flags prevent re-configuration when configure() is called with new parameters.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/assertion.ts
Comment on lines +92 to +95
test?.info().annotations.push({
type: "AI Summary (video analysis)",
description: videoResult.reasoning,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Unsafe property access test?.info().annotations when test is undefined. If test is not provided, test?.info() will evaluate to undefined, and attempting to access .annotations on it will throw a TypeError. Use optional chaining to safely access the annotations array.

    test?.info()?.annotations.push({\n      type: \"AI Summary (video analysis)\",\n      description: videoResult.reasoning,\n    });

Comment thread src/redis.ts
Comment on lines +5 to +31
let client: Redis | null = null;
let initialized = false;

/**
* Returns a memoized Redis client. Reads `configure({ redis: { url } })` first,
* then falls back to `process.env.REDIS_URL`. Returns null when neither is set,
* which disables step caching, {{global.*}} placeholders, and project data.
*
* Lazy: the connection is opened on first call so users can call `configure()`
* before any Redis-dependent code path runs.
*/
export function getRedis(): Redis | null {
if (initialized) return client;
initialized = true;

const url = getConfig().redis?.url ?? process.env.REDIS_URL;
if (!url) {
logger.warn(
"Redis URL not set (configure({ redis: { url } }) or REDIS_URL). " +
"Step caching, global placeholders, and project data are disabled.",
);
return null;
}

client = new Redis(url);
return client;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Caching the initialized state prevents re-configuration of the Redis client when configure() is called with a new URL. Instead of a static initialized flag, track the active URL and re-initialize the client if the URL changes.

let client: Redis | null = null;\nlet activeUrl: string | undefined = undefined;\n\n/**\n * Returns a memoized Redis client. Reads `configure({ redis: { url } })` first,\n * then falls back to `process.env.REDIS_URL`. Returns null when neither is set,\n * which disables step caching, {{global.*}} placeholders, and project data.\n *\n * Lazy: the connection is opened on first call so users can call `configure()`\n * before any Redis-dependent code path runs.\n */\nexport function getRedis(): Redis | null {\n  const url = getConfig().redis?.url ?? process.env.REDIS_URL;\n  \n  if (url !== activeUrl) {\n    if (client) {\n      client.disconnect();\n      client = null;\n    }\n    activeUrl = url;\n    if (url) {\n      client = new Redis(url);\n    } else {\n      logger.warn(\n        \"Redis URL not set (configure({ redis: { url } }) or REDIS_URL). \" +\n          \"Step caching, global placeholders, and project data are disabled.\",\n      );\n    }\n  }\n  return client;\n}

Comment thread src/cache.ts
Comment on lines +121 to 167
let cacheInstance: CacheStore | null = null;
let initialized = false;

/**
* Creates the cache store based on environment variables.
* Returns a memoized cache store based on the `CACHE_PROVIDER` env var:
* - "redis" (also the default): uses Redis via `getRedis()`, which honors
* `configure({ redis: { url } })` and `REDIS_URL`.
* - "file": uses JSON files on disk at `CACHE_DIR` (defaults to
* `.passmark-cache`). Handy for committing a warm cache to Git for CI.
* - "none": disables caching entirely.
*
* CACHE_PROVIDER selects the backend:
* - "redis" (default when REDIS_URL is set): uses Redis via ioredis
* - "file": uses JSON files on disk at CACHE_DIR (defaults to .passmark-cache)
* - "none": disables caching entirely
* Returns null when caching is disabled or no Redis URL is configured, which
* disables step caching, {{global.*}} placeholders, and project data.
*
* For backwards compatibility, if CACHE_PROVIDER is not set:
* - If REDIS_URL is set → uses Redis
* - Otherwise → caching is disabled (null)
* Lazy: resolved on first call so users can call `configure()` beforehand.
*/
function createCacheStore(): CacheStore | null {
export function getCache(): CacheStore | null {
if (initialized) return cacheInstance;
initialized = true;

const provider = process.env.CACHE_PROVIDER?.toLowerCase();

if (provider === "none") {
logger.warn("Cache provider set to 'none'. Caching is disabled.");
return null;
logger.warn("CACHE_PROVIDER is 'none'. Caching is disabled.");
cacheInstance = null;
return cacheInstance;
}

if (provider === "file") {
const dir = process.env.CACHE_DIR || ".passmark-cache";
logger.info(`Using file-based cache at: ${dir}`);
return new FileStore(dir);
}

if (provider === "redis" || (!provider && process.env.REDIS_URL)) {
if (!process.env.REDIS_URL) {
logger.warn("CACHE_PROVIDER is 'redis' but REDIS_URL is not set. Caching is disabled.");
return null;
}
logger.info("Using Redis cache.");
return new RedisStore(process.env.REDIS_URL);
cacheInstance = new FileStore(dir);
return cacheInstance;
}

if (provider) {
if (provider && provider !== "redis") {
logger.warn(`Unknown CACHE_PROVIDER '${provider}'. Caching is disabled.`);
return null;
cacheInstance = null;
return cacheInstance;
}

// No CACHE_PROVIDER and no REDIS_URL
logger.warn(
"No cache provider configured. Set CACHE_PROVIDER=redis|file|none or REDIS_URL. " +
"Step caching, global placeholders, and project data are disabled.",
);
return null;
// Default ("redis" or unset): use the configured Redis client. getRedis()
// already warns when no URL is set.
const redis = getRedis();
cacheInstance = redis ? new RedisStore(redis) : null;
return cacheInstance;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Caching the initialized state prevents re-configuration of the Cache store when configure() or env vars are updated. Track the active provider, directory, and redis client to dynamically re-initialize the cache store when configuration changes.

let cacheInstance: CacheStore | null = null;\nlet activeProvider: string | undefined = undefined;\nlet activeDir: string | undefined = undefined;\nlet activeRedis: any = undefined;\n\n/**\n * Returns a memoized cache store based on the `CACHE_PROVIDER` env var:\n *   - \"redis\" (also the default): uses Redis via `getRedis()`, which honors\n *     `configure({ redis: { url } })` and `REDIS_URL`.\n *   - \"file\": uses JSON files on disk at `CACHE_DIR` (defaults to\n *     `.passmark-cache`). Handy for committing a warm cache to Git for CI.\n *   - \"none\": disables caching entirely.\n *\n * Returns null when caching is disabled or no Redis URL is configured, which\n * disables step caching, {{global.*}} placeholders, and project data.\n *\n * Lazy: resolved on first call so users can call `configure()` beforehand.\n */\nexport function getCache(): CacheStore | null {\n  const provider = process.env.CACHE_PROVIDER?.toLowerCase();\n  const dir = process.env.CACHE_DIR || \".passmark-cache\";\n  const redis = getRedis();\n\n  if (\n    provider !== activeProvider ||\n    (provider === \"file\" && dir !== activeDir) ||\n    (provider !== \"none\" && provider !== \"file\" && redis !== activeRedis)\n  ) {\n    activeProvider = provider;\n    activeDir = dir;\n    activeRedis = redis;\n\n    if (provider === \"none\") {\n      logger.warn(\"CACHE_PROVIDER is 'none'. Caching is disabled.\");\n      cacheInstance = null;\n    } else if (provider === \"file\") {\n      logger.info(`Using file-based cache at: ${dir}`);\n      cacheInstance = new FileStore(dir);\n    } else if (provider && provider !== \"redis\") {\n      logger.warn(`Unknown CACHE_PROVIDER '${provider}'. Caching is disabled.`);\n      cacheInstance = null;\n    } else {\n      cacheInstance = redis ? new RedisStore(redis) : null;\n    }\n  }\n  return cacheInstance;\n}

Comment thread src/cua/actions.ts
Comment on lines +76 to +77
export async function executeAction(page: Page, action: ComputerAction): Promise<void> {
switch (action.type) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Missing null/undefined check on action in executeAction can cause a TypeError crash if an empty or null action is passed. Add a defensive guard at the beginning of the function.

export async function executeAction(page: Page, action: ComputerAction): Promise<void> {\n  if (!action) return;\n  switch (action.type) {

Comment thread src/video.ts
Comment on lines +43 to +46
await resolvePage(this.page).screencast.start({
path: this.filePath,
size: { width: VIDEO_DEFAULT_WIDTH, height: VIDEO_DEFAULT_HEIGHT },
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Unsafe access to screencast on Playwright's Page object without checking if it is supported. screencast is an internal, undocumented Chromium-only API. If the tests are run on Firefox or WebKit, this will throw a TypeError. Add a check to verify screencast is supported before starting the recording.

    const pageInstance = resolvePage(this.page) as any;\n    if (!pageInstance.screencast) {\n      throw new Error(\"Screencast is not supported on this browser/platform (Chromium only)\");\n    }\n    await pageInstance.screencast.start({\n      path: this.filePath,\n      size: { width: VIDEO_DEFAULT_WIDTH, height: VIDEO_DEFAULT_HEIGHT },\n    });

@rlweb rlweb marked this pull request as ready for review June 15, 2026 15:14
@rlweb rlweb merged commit e23713a into cache-plugin Jun 15, 2026
@rlweb rlweb deleted the claude/nice-bohr-a485n8 branch June 15, 2026 15:15
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.

8 participants