Skip to content

Commit 6456db7

Browse files
jirispilkaclaude
andauthored
fix: Report an empty search-actors page past the last result as such (#1568)
## Why Closes #1276 Review page with before/after examples for this PR and the five related ones (#1563–#1567): https://claude.ai/artifact/WDRj1nwPnMTP9ktGyg3z3i Reported by @lokicik. This follows the implementation spec in the maintainer comment on the issue. `search-actors` returned the no-match guidance ("Keywords can include … A platform-only query such as "TikTok" …") for any empty page, including a page past the last result of a query that matched. With an empty query that advice cannot help, and it reads as if nothing matched. ## What changed `buildNoActorsFoundInstructions()`, shared by `search-actors` and `search-actors-widget`, now gets the offset. With `offset > 0` it returns one sentence and no keyword advice; at `offset` 0 the text is unchanged. No new response fields; the output schema is unchanged. This is a tool-text change to `structuredContent.instructions` and the text content. `search-actors` with `keywords: "", limit: 1, offset: 1000000`: ``` Before: No Actors were found for the search query "". Keywords can include the named platform and/or data type (e.g. "TikTok posts"). A platform-only query such as "TikTok" is an option if more specific results do not fit. Now: No Actors past offset 1000000 for the search query "". ``` ## Notes for reviewers (human-written) ## Proof it works mcpc with the Claude connector tool set on this branch: | `search-actors` call | Result | |---|---| | `keywords: "", limit: 1, offset: 0` | 1 Actor | | `keywords: "", limit: 1, offset: 1000000` | `No Actors past offset 1000000 for the search query "".` | | `keywords: "instagram scraper", limit: 5, offset: 5000` | `No Actors past offset 5000 for the search query "instagram scraper".` | | `keywords: "zzqqxxnotarealactorkeyword123", offset: 0` | the three-line no-match guidance, unchanged | The text content equals `structuredContent.instructions`, and `structuredContent` keeps its fields (`actors: []`, `query`, `count: 0`, `instructions`). The same `offset: 1000000` call on a build with master's text returns the three-line guidance shown under "Before". Unit tests (failing before the fix): an empty page at `offset` 1 returns the new sentence in `structuredContent` and the text content, for both `search-actors` and `search-actors-widget`. The existing empty-first-page tests keep the three-line text. Implemented with Claude Code: code, tests, mcpc probe and this description. Reviewed by a separate Claude Code review pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_0149C4bPjve1UHWcxTtBsPXC --- _Generated by [Claude Code](https://claude.ai/code/session_0149C4bPjve1UHWcxTtBsPXC)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3a5570d commit 6456db7

4 files changed

Lines changed: 45 additions & 4 deletions

File tree

‎src/tools/actors/search_actors.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,11 @@ export function buildSearchActorsResult(
124124

125125
/**
126126
* Builds the empty-results guidance message for when no Actors are found.
127-
* Interpolates the search keywords into the message.
127+
* Past offset 0 the query may have matched and the page only lies past the last result, so keyword
128+
* guidance does not apply (see apify/apify-mcp-server#1276).
128129
*/
129-
export function buildNoActorsFoundInstructions(keywords: string): string {
130+
export function buildNoActorsFoundInstructions(keywords: string, offset: number): string {
131+
if (offset > 0) return `No Actors past offset ${offset} for the search query "${keywords}".`;
130132
return dedent`
131133
No Actors were found for the search query "${keywords}".
132134
Keywords can include the named platform and/or data type (e.g. "TikTok posts").
@@ -207,7 +209,7 @@ export const searchActors: ToolEntry = Object.freeze({
207209
]);
208210

209211
if (actors.length === 0) {
210-
const instructions = buildNoActorsFoundInstructions(parsed.keywords);
212+
const instructions = buildNoActorsFoundInstructions(parsed.keywords, parsed.offset);
211213
return respondOk(instructions, {
212214
structuredContent: { actors: [], query: parsed.keywords, count: 0, instructions },
213215
});

‎src/tools/widgets/search_actors_widget.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const searchActorsWidget: ToolEntry = Object.freeze({
8484
if (actors.length === 0) {
8585
// Honor the widget tool's declared outputSchema: widgetActors is required, so
8686
// emit an empty array rather than dropping the field.
87-
const instructions = buildNoActorsFoundInstructions(parsed.keywords);
87+
const instructions = buildNoActorsFoundInstructions(parsed.keywords, parsed.offset);
8888
return respondOk(instructions, {
8989
structuredContent: {
9090
actors: [],

‎tests/unit/tools.search_actors.default.response.test.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,24 @@ describe('search-actors without widget (searchActors)', () => {
187187
expect(content[0].text).toBe(NO_ACTORS_FOUND_TEXT);
188188
});
189189

190+
it('says that no Actors are past the offset, without keyword guidance', async () => {
191+
vi.mocked(searchAgentSafeActors).mockResolvedValue([]);
192+
193+
const result = await (searchActors as HelperTool).call(
194+
stubToolCallContext({ keywords: SEARCH_KEYWORDS, limit: 5, offset: 1 }, mockApifyClient(), {
195+
loadedToolNames: [],
196+
}),
197+
);
198+
199+
const { structuredContent, content } = result as {
200+
structuredContent: Record<string, unknown>;
201+
content: { type: string; text: string }[];
202+
};
203+
const text = `No Actors past offset 1 for the search query "${SEARCH_KEYWORDS}".`;
204+
expect(structuredContent).toEqual({ actors: [], query: SEARCH_KEYWORDS, count: 0, instructions: text });
205+
expect(content).toEqual([{ type: 'text', text }]);
206+
});
207+
190208
it('declares every field the structured card emits (guards schema/runtime drift)', () => {
191209
// Regression guard for #889: the advertised output schema must declare every field
192210
// the runtime card actually emits. `pictureUrl` was emitted but undeclared — this

‎tests/unit/tools.search_actors.widget.response.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,27 @@ describe('search-actors-widget response', () => {
115115
expect(_meta).toBeUndefined();
116116
});
117117

118+
it('says that no Actors are past the offset, without keyword guidance', async () => {
119+
vi.mocked(searchAgentSafeActors).mockResolvedValue([]);
120+
121+
const result = await (searchActorsWidget as HelperTool).call(
122+
stubToolCallContext({ keywords: SEARCH_KEYWORDS, limit: 5, offset: 1 }, mockApifyClient(), {
123+
loadedToolNames: [],
124+
}),
125+
);
126+
127+
const { structuredContent, content, _meta } = result as {
128+
structuredContent: Record<string, unknown>;
129+
content: { type: string; text: string }[];
130+
_meta?: unknown;
131+
};
132+
expect(structuredContent).toEqual({ actors: [], query: SEARCH_KEYWORDS, count: 0, widgetActors: [] });
133+
expect(content).toEqual([
134+
{ type: 'text', text: `No Actors past offset 1 for the search query "${SEARCH_KEYWORDS}".` },
135+
]);
136+
expect(_meta).toBeUndefined();
137+
});
138+
118139
it('appends the not-runnable caveat when call-actor is absent', async () => {
119140
vi.mocked(searchAgentSafeActors).mockResolvedValue([MOCK_STORE_ACTOR]);
120141

0 commit comments

Comments
 (0)