Skip to content

Let the installer that opens the PR see what the picker sees - #220

Merged
ralyodio merged 2 commits into
masterfrom
ad-install-tree-fallback
Aug 29, 2026
Merged

Let the installer that opens the PR see what the picker sees#220
ralyodio merged 2 commits into
masterfrom
ad-install-tree-fallback

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Follow-on to deb65fe, which landed the git-tree walk while this was in flight. That commit put the walk in findInstallCandidates — what the picker calls. installTracker, the function that actually opens the stats PR, still had only the canonical list and code search, so the two flows disagreed: the picker could show a publisher their shell at apps/web/src/components/Layout.jsx while a direct install insisted the repo had no template file at all.

What changed

installTracker walks the tree too, before code search, since search does not index private repos. Same ranking, same read budget.

It stops running code search once the repo turns out to be installed already. A hit elsewhere would have had it inject a second copy into an unrelated file.

Dropped templates? from the -100 path penalty. For Django, Flask, Jinja and Rails that directory is exactly where the document shell lives, so the penalty buried the only installable file in the repo underneath everything else. A real app/layout.tsx still outranks it.

The tree walk now reads a basename two ways. Stripping one extension off application.html.erb leaves application.html, which matches no shell name — so Rails and Laravel were invisible to the gate. Added footer for WordPress themes, and named all three in the canonical list too.

One tree fetch, used twice. findInstallCandidates fetched the tree for the walk but still probed the canonical list blind. Knowing which files exist turns ~35 serial misses into one read per real file, and the probes now run in parallel. A truncated tree is treated as proving nothing, so the full list is still probed there.

Tests

The mock in install-tracker.test.ts had no listRepoTree, so the tree walk was throwing on an undefined function, getting swallowed by its own catch, and every test was exercising the old canonical-probe path instead of the one that ships. Wired up, plus coverage for the tree fallback in installTracker, the already-installed guard, the templates/ ranking, an unavailable tree, and a truncated one.

Verification

pnpm install still fails at HEAD (the @profullstack/autoblog pin), so there is no vitest or tsc in this checkout. The changed source was bundled unmodified with the esbuild that is in node_modules and run against a fake GitHub API — 22 checks, all passing:

  • Rails, Laravel and WordPress shells are found
  • templates/base.html is offered and no longer scores -100; app/layout.tsx still outranks it
  • a repo with one layout costs exactly one file read
  • tree unavailable → falls back to the full canonical probe; truncated tree → same
  • rootPath still scopes discovery, with no double-prefixed probe
  • dist/ and node_modules/ never offered
  • installTracker opens a PR at a tree-only shell, and leaves an already-installed repo alone even when code search offers another file
  • ad install is unchanged on an ordinary repo: units above </body>, one loader

The existing installTracker tests all pass an explicit targetPath, so they skip discovery and are unaffected.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LT2u8KFbyeicqE9z2kUHJR

Follow-on to deb65fe. The tree walk landed in findInstallCandidates, which
is what the picker calls. installTracker — the function that actually opens
the stats PR — still had only the canonical list and code search, so the two
flows disagreed: the picker could show a publisher their shell at
apps/web/src/components/Layout.jsx while a direct install insisted the repo
had no template file at all.

- installTracker walks the tree too, before code search, since search does
  not index private repos. Same ranking, same read budget.
- It also stops running code search once the repo turns out to be installed
  already; a hit elsewhere would have injected a second copy into an
  unrelated file.
- Drop `templates?` from the -100 path penalty. For Django, Flask, Jinja and
  Rails that directory is exactly where the shell lives, and the penalty
  buried the only installable file in the repo. A real layout still outranks
  it.
- Read the basename two ways in the tree walk. Stripping one extension off
  application.html.erb leaves "application.html", which matches no shell
  name, so Rails and Laravel were invisible; add `footer` for WordPress
  themes, and name all three in the canonical list as well.
- Fetch the tree once at the top of findInstallCandidates and reuse it for
  both the walk and the canonical probes. Knowing which files exist turns
  ~35 serial misses into one read per real file, and the probes now run in
  parallel.

Tests: the mock in install-tracker.test.ts had no listRepoTree, so the tree
walk was throwing on an undefined function, being swallowed by its own
catch, and every test was exercising the old canonical path instead of the
one that ships. Wired up, plus coverage for the tree fallback, the
already-installed guard, the templates ranking, an unavailable tree, and a
truncated one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LT2u8KFbyeicqE9z2kUHJR
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

40 finding(s)

HIGH/CRITICAL: 3 | MEDIUM: 28 | LOW: 9

Severity Rule Location
HIGH tls-verification-disabled lib/onion.ts:47
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
HIGH sh-remote-script-execution prober/deploy/provision.sh:30
MEDIUM js-unescaped-html-sink app/(app)/dashboard/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM js-unescaped-html-sink app/(app)/dashboard/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:77
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:57
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM redos-nested-quantifier lib/careers/jobs.ts:139
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:130
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:98
MEDIUM redos-nested-quantifier lib/sp/parseHandle.ts:92
MEDIUM redos-nested-quantifier lib/tracker/agent-gate.ts:61
MEDIUM sql-template-interpolation scripts/detect-slot-themes.ts:31
MEDIUM sql-template-interpolation scripts/purge-constructed-keywords.ts:163
MEDIUM sql-template-interpolation scripts/purge-offniche-keywords.ts:124
LOW secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
LOW secret-generic-credential lib/sp/platforms/linkedin.ts:25
LOW js-dynamic-code-execution tests/careers-page-templates.test.ts:21
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:19
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:51
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:52
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

An empty default return infers never[], so mockResolvedValueOnce with a
real hit failed tsc --noEmit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LT2u8KFbyeicqE9z2kUHJR
@ralyodio
ralyodio merged commit d520f4b into master Aug 29, 2026
10 checks passed
@ralyodio
ralyodio deleted the ad-install-tree-fallback branch August 29, 2026 05:58
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