This wiki page is less of a “how to” and more of a “this is what I looked at, this is what I tried, and this is what I found”. This is mostly investigative exploratory work, and if this results in helpful tools and processes for other devs at Sage, that’s just a nice side bonus.
Executive Summary
Worth investigating on a limited basis
https://github.com/features/copilot - Continue passive investigation as part of my 60-day free trial while working on normal sprint tasks. Seems most promising for enumerating test cases.
https://www.diffblue.com/products/ - Might be valuable for unit testing simple functions, but inadequate for anything complex. Almost certainly not worth the paid tier.
Investigation blocked
https://codeball.ai/ - Ran into issues setting up Codeball. Devs have not yet responded to requests for support.
https://www.codiga.io/ - Requires org-level GitHub permissions to install, even if I’m only installing on repositories that I have admin access over. Currently waiting on IT to respond to install request.
https://aws.amazon.com/codeguru/ - CodeGuru attempts to connect to the wrong fork of our repo. Blocked waiting for AWS Support to respond.
Not worth further investigation
https://platform.openai.com/docs/guides/code - Inadequate. Maybe try again in 2024 or later when AI research has improved.
Update: GPT-4 was released recently and might be worth looking into.
Code Generation
OpenAI
OpenAI is best known for their product ChatGPT. However, they have models that can be used for code generation. See https://platform.openai.com/docs/guides/code . Initial investigations into OpenAI were underwhelming. The better of the two models (code-davinci-002) was frequently under load and unavailable, so I did my investigations with the lesser of the two models (code-cushman-001, described as “almost as capable as code-davinci-002, but slightly faster”).
OpenAI is currently free, but the page implies that it will stop being free once it’s out of beta.
I used it to try to generate unit tests, and the code it generated was repetitive, incomplete, and wouldn’t even compile. For reference, the code under test was https://github.com/DwayneJengSage/BridgeServer2/blob/develop/src/main/java/org/sagebionetworks/bridge/services/UploadService.java#L522
This is the prompt I provided
// Generate unit tests for https://github.com/DwayneJengSage/BridgeServer2/blob/develop/src/main/java/org/sagebionetworks/bridge/services/UploadService.java#L522 @Test public void testGetUploadViewForExporter3() {
This is what OpenAI returned
@Test public void testGetUploadViewForExporter3() { UploadView view = uploadService.getUploadViewForExporter(EXPORTER_3_ID, UPLOAD_ID_3); assertEquals(UPLOAD_ID_3, view.getUploadId()); assertEquals(EXPORTER_3_ID, view.getExporterId()); assertEquals(EXPORTER_3_NAME, view.getExporterName()); assertEquals(EXPORTER_3_DESCRIPTION, view.getExporterDescription()); assertEquals(EXPORTER_3_VERSION, view.getExporterVersion()); assertEquals(EXPORTER_3_LICENSE, view.getExporterLicense()); assertEquals(EXPORTER_3_LICENSE_URL, view.getExporterLicenseUrl()); assertEquals(EXPORTER_3_LICENSE_LOGO_URL, view.getExporterLicenseLogoUrl()); assertEquals(EXPORTER_3_LICENSE_LOGO_URL, view.getExporterLicenseLogoUrl()); assertEquals(EXPORTER_3_LICENSE_LOGO_URL, view.getExporterLicenseLogoUrl()); // repeating the last line until we ran out of tokens
Instead of linking to our code, I attempted to copy the code into OpenAI as a prompt. However, the code in question requires 1500 tokens, leaving us only 500 tokens remaining. I gave the following prompt
// Unit test with mocks. @Test public void testGetUploadViewForExporter3() {
OpenAI returned the following
// Unit test with mocks. @Test public void testGetUploadViewForExporter3() { String appId = "appId"; String studyId = "studyId"; String uploadId = "uploadId"; String healthCode = "healthCode"; String userId = "userId"; Upload upload = new Upload(); upload.setId(uploadId); upload.setAppId(appId); upload.setHealthCode(healthCode); upload.setMetadata(new ObjectNode(JsonNodeFactory.instance)); HealthDataRecordEx3 record = new HealthDataRecordEx3(); record.setId(uploadId); record.setAppId(appId); record.setHealthCode(healthCode); record.setMetadata(new HashMap<>()); UploadViewEx3 view = new UploadViewEx3(); view.setId(uploadId); view.setHealthCode(healthCode); view.setRecord(record); view.setUpload(upload); view.setUserId(userId); UploadViewEx3 view2 = new UploadViewEx3(); view2.setId(uploadId); view2.setHealthCode(healthCode); view2.setRecord(record); view2.setUpload(upload); view2.setUserId(userId); UploadViewEx3 view3 = new UploadViewEx3(); // repeat making UploadViewEx3's until we ran out of tokens
Conclusion: OpenAI isn’t ready for code generation, at least not as is.
GitHub Copilot
GitHub Copilot was similar to OpenAI in that both ended up writing code that was repetitive and didn’t compile and needed a lot of editing to get it to compile and run. The one advantage Copilot has over OpenAI is that Copilot can run as an IDE plugin.
In terms of cost, GitHub Copilot costs $10/mo, which is less than 0.2% of an entry-level Associate Engineer’s salary at Sage.
I tried Copilot specifically for unit test generation. For reference, the code under test was https://github.com/DwayneJengSage/BridgeServer2/blob/develop/src/main/java/org/sagebionetworks/bridge/services/UploadService.java#L522
I went to UploadServiceTest.java and gave the following prompt
// Unit test for UploadService.getUploadViewForExporter3() with timeline and adherence. @Test public void testGetUploadViewForExporter3() {
Copilot filled this in with the following
// Unit test for UploadService.getUploadViewForExporter3() with timeline and adherence. @Test public void testGetUploadViewForExporter3() { // Mock upload. Upload upload = Upload.create(); upload.setAppId(TEST_APP_ID); upload.setUploadId(UPLOAD_ID_1); upload.setHealthCode(HEALTH_CODE); upload.setStudyId(TEST_STUDY_ID); upload.setCreatedOn(CREATED_ON); upload.setCompletedOn(CREATED_ON); upload.setMetadata(METADATA); upload.setRecordId(RECORD_ID); when(mockUploadDao.getUploadNoThrow(UPLOAD_ID_1)).thenReturn(upload); // Mock record. HealthDataRecordEx3 healthDataRecord = HealthDataRecordEx3.create(); healthDataRecord.setAppId(TEST_APP_ID); healthDataRecord.setUploadId(UPLOAD_ID_1); healthDataRecord.setHealthCode(HEALTH_CODE); healthDataRecord.setStudyId(TEST_STUDY_ID); healthDataRecord.setCreatedOn(CREATED_ON); healthDataRecord.setMetadata(METADATA); when(mockHealthDataEx3Service.getRecord(UPLOAD_ID_1, false)).thenReturn(Optional.of( healthDataRecord)); // Mock timeline. TimelineMetadata timelineMetadata = new TimelineMetadata(); timelineMetadata.setAppVersion(1); timelineMetadata.setPhoneInfo("phone info"); timelineMetadata.setTimeZone("America/Los_Angeles"); timelineMetadata.setUploadDate(CREATED_ON); timelineMetadata.setUploadId(UPLOAD_ID_1); timelineMetadata.setUploadMetadata(METADATA); when(mockTimelineService.getTimelineMetadata(UPLOAD_ID_1)).thenReturn(Optional.of( timelineMetadata)); // Mock adherence. AdherenceRecord adherenceRecord = new AdherenceRecord(); adherenceRecord.setAppVersion(1); adherenceRecord.setPhoneInfo("phone info"); adherenceRecord.setTimeZone("America/Los_Angeles"); adherenceRecord.setUploadDate(CREATED_ON); adherenceRecord.setUploadId(UPLOAD_ID_1); adherenceRecord.setUploadMetadata(METADATA); when(mockAdherenceService.getAdherenceRecord(UPLOAD_ID_1)).thenReturn(Optional.of( adherenceRecord)); // Execute and validate. UploadView uploadView = svc.getUploadViewForExporter3(TEST_APP_ID, null, UPLOAD_ID_1, true, true); assertEquals(uploadView.getUploadId(), UPLOAD_ID_1); assertEquals(uploadView.getHealthCode(), HEALTH_CODE); assertEquals(uploadView.getStudyId(), TEST_STUDY_ID); }
This looks really nice, except that most of the setters on Upload, Record, Timeline, and Adherence don’t actually exist, nor do the getters on UploadView. Also, by the logic of the code under test, this wouldn’t actually exercise the code under test.
At Nick Grosenbacher’s suggestion, I attempted to use Copilot to enumerate test cases. I gave the following prompt
// Generate test cases for UploadService.getUploadViewForExporter3(). // test case:
Copilot auto-completed it to
// test case: upload, record, expected exception
And then repeated it a bunch of times.
Okay, let’s trye
// test case 2:
Copilot auto-completed it to the same thing
// test case 2: upload, record, expected exception
Okay, maybe my mistake was asking Copilot to enumerate test cases in a file separate from where my code is. Let’s try putting this prompt in a comment immediately after the method I’m trying to test
// Generate test cases for getUploadViewForExporter3(). // test case:
Copilot auto-completed it with
// test case: getUploadViewForExporter3() with upload // test case: getUploadViewForExporter3() with record // test case: getUploadViewForExporter3() with upload and record // test case: getUploadViewForExporter3() with upload and record, but upload is from a different app // test case: getUploadViewForExporter3() with upload and record, but record is from a different app // test case: getUploadViewForExporter3() with upload and record, but upload and record are from different apps // (repeats the last line ad infinitum)
Okay, it’s nowhere near exhaustive. There are a lot of important cases that it’s missing, but it’s a good start.
Conclusion: Copilot is worth continuing to passively investigate while working on normal sprint work. It seems okay for enumerating test cases, but it’s incomplete, and I wouldn’t yet trust it to generate code for me.
DiffBlue Cover
See https://www.diffblue.com/products/ DiffBlue Cover is a tool to analyze code and generate unit tests. It exists as an IntelliJ plugin.
Some limitations of DiffBlue Cover
Unit tests generated by DiffBlue and human-written unit tests should be in separate files, or else the diffs get messy.
DiffBlue takes a hard dependency on JUnit 4 or 5. We currently use TestNG. This isn’t a problem, but it does require setup to have a project with mixed tests.
There’s a free tier, but the paid tier is expensive. $56k/yr. For that price, we could get about 70% of an entry-level Associate Engineer at Sage.
I pointed DiffBlue at For reference, the code under test was https://github.com/DwayneJengSage/BridgeServer2/blob/develop/src/main/java/org/sagebionetworks/bridge/services/UploadService.java#L522 and told it to generate tests. DiffBlue created the following
package org.sagebionetworks.bridge.services; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.amazonaws.services.s3.AmazonS3; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.Optional; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.sagebionetworks.bridge.config.BridgeConfig; import org.sagebionetworks.bridge.dao.UploadDao; import org.sagebionetworks.bridge.dao.UploadDedupeDao; import org.sagebionetworks.bridge.dynamodb.DynamoHealthDataRecordEx3; import org.sagebionetworks.bridge.dynamodb.DynamoUpload2; import org.sagebionetworks.bridge.exceptions.BadRequestException; import org.sagebionetworks.bridge.exceptions.EntityNotFoundException; import org.sagebionetworks.bridge.models.BridgeEntity; import org.sagebionetworks.bridge.models.upload.UploadViewEx3; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class UploadServiceDiffBlueTest { @Mock private AccountService accountService; @Mock private AdherenceService adherenceService; @Mock private AmazonS3 amazonS3; @Mock private AppService appService; @Mock private BridgeConfig bridgeConfig; @Mock private Exporter3Service exporter3Service; @Mock private HealthDataEx3Service healthDataEx3Service; @Mock private HealthDataService healthDataService; @Mock private Schedule2Service schedule2Service; @Mock private UploadDao uploadDao; @Mock private UploadDedupeDao uploadDedupeDao; @InjectMocks private UploadService uploadService; @Mock private UploadValidationService uploadValidationService; /** * Method under test: {@link UploadService#getUploadViewForExporter3(String, String, String, boolean, boolean)} */ @Test void testGetUploadViewForExporter3() { when(healthDataEx3Service.getRecord((String) any(), anyBoolean())) .thenReturn(Optional.of(new DynamoHealthDataRecordEx3())); when(uploadDao.getUploadNoThrow((String) any())).thenReturn(new DynamoUpload2()); assertThrows(EntityNotFoundException.class, () -> uploadService.getUploadViewForExporter3("42", "42", "42", true, true)); verify(uploadDao).getUploadNoThrow((String) any()); } /** * Method under test: {@link UploadService#getUploadViewForExporter3(String, String, String, boolean, boolean)} */ @Test void testGetUploadViewForExporter32() { when(healthDataEx3Service.getRecord((String) any(), anyBoolean())) .thenReturn(Optional.of(new DynamoHealthDataRecordEx3())); when(uploadDao.getUploadNoThrow((String) any())).thenThrow(new RuntimeException()); assertThrows(RuntimeException.class, () -> uploadService.getUploadViewForExporter3("42", "42", "42", true, true)); verify(uploadDao).getUploadNoThrow((String) any()); } /** * Method under test: {@link UploadService#getUploadViewForExporter3(String, String, String, boolean, boolean)} */ @Test void testGetUploadViewForExporter33() { when(healthDataEx3Service.getRecord((String) any(), anyBoolean())) .thenReturn(Optional.of(new DynamoHealthDataRecordEx3())); when(uploadDao.getUploadNoThrow((String) any())).thenReturn(null); assertThrows(EntityNotFoundException.class, () -> uploadService.getUploadViewForExporter3("42", "42", "42", true, true)); verify(healthDataEx3Service).getRecord((String) any(), anyBoolean()); verify(uploadDao).getUploadNoThrow((String) any()); } /** * Method under test: {@link UploadService#getUploadViewForExporter3(String, String, String, boolean, boolean)} */ @Test void testGetUploadViewForExporter34() { when(healthDataEx3Service.getRecord((String) any(), anyBoolean())) .thenReturn(Optional.of(new DynamoHealthDataRecordEx3())); DynamoUpload2 dynamoUpload2 = mock(DynamoUpload2.class); when(dynamoUpload2.getAppId()).thenReturn("42"); when(uploadDao.getUploadNoThrow((String) any())).thenReturn(dynamoUpload2); assertThrows(EntityNotFoundException.class, () -> uploadService.getUploadViewForExporter3("42", "42", "42", true, true)); verify(healthDataEx3Service).getRecord((String) any(), anyBoolean()); verify(uploadDao).getUploadNoThrow((String) any()); verify(dynamoUpload2).getAppId(); } /** * Method under test: {@link UploadService#getUploadViewForExporter3(String, String, String, boolean, boolean)} */ @Test void testGetUploadViewForExporter35() { when(healthDataEx3Service.getRecord((String) any(), anyBoolean())) .thenReturn(Optional.of(mock(DynamoHealthDataRecordEx3.class))); DynamoUpload2 dynamoUpload2 = mock(DynamoUpload2.class); when(dynamoUpload2.getAppId()).thenThrow(new RuntimeException()); when(uploadDao.getUploadNoThrow((String) any())).thenReturn(dynamoUpload2); assertThrows(RuntimeException.class, () -> uploadService.getUploadViewForExporter3("42", "42", "42", true, true)); verify(uploadDao).getUploadNoThrow((String) any()); verify(dynamoUpload2).getAppId(); } /** * Method under test: {@link UploadService#getUploadViewForExporter3(String, String, String, boolean, boolean)} */ @Test @Disabled("TODO: Complete this test") void testGetUploadViewForExporter36() { // TODO: Complete this test. // Reason: R013 No inputs found that don't throw a trivial exception. // Diffblue Cover tried to run the arrange/act section, but the method under // test threw // java.lang.NullPointerException // at org.sagebionetworks.bridge.services.UploadService.getUploadViewForExporter3(UploadService.java:549) // See https://diff.blue/R013 to resolve this issue. when(healthDataEx3Service.getRecord((String) any(), anyBoolean())).thenReturn(null); when(uploadDao.getUploadNoThrow((String) any())).thenReturn(mock(DynamoUpload2.class)); new RuntimeException(); uploadService.getUploadViewForExporter3("42", "42", "42", true, true); } /** * Method under test: {@link UploadService#getUploadViewForExporter3(String, String, String, boolean, boolean)} */ @Test void testGetUploadViewForExporter37() { when(accountService.getAccountId((String) any(), (String) any())).thenReturn(Optional.of("foo")); when(healthDataEx3Service.getRecord((String) any(), anyBoolean())).thenReturn(Optional.empty()); DynamoUpload2 dynamoUpload2 = mock(DynamoUpload2.class); when(dynamoUpload2.getMetadata()).thenReturn(new ObjectNode(JsonNodeFactory.withExactBigDecimals(true))); when(dynamoUpload2.getHealthCode()).thenReturn("Health Code"); when(dynamoUpload2.getAppId()).thenReturn("42"); when(uploadDao.getUploadNoThrow((String) any())).thenReturn(dynamoUpload2); new RuntimeException(); UploadViewEx3 actualUploadViewForExporter3 = uploadService.getUploadViewForExporter3("42", "42", "42", true, true); assertEquals("foo", actualUploadViewForExporter3.getUserId()); assertNull(actualUploadViewForExporter3.getRecord()); assertEquals("42", actualUploadViewForExporter3.getId()); assertEquals("Health Code", actualUploadViewForExporter3.getHealthCode()); verify(accountService).getAccountId((String) any(), (String) any()); verify(healthDataEx3Service).getRecord((String) any(), anyBoolean()); verify(uploadDao).getUploadNoThrow((String) any()); verify(dynamoUpload2).getMetadata(); verify(dynamoUpload2).getAppId(); verify(dynamoUpload2).getHealthCode(); } /** * Method under test: {@link UploadService#getUploadViewForExporter3(String, String, String, boolean, boolean)} */ @Test void testGetUploadViewForExporter38() { when(accountService.getAccountId((String) any(), (String) any())).thenReturn(Optional.of("foo")); when(healthDataEx3Service.getRecord((String) any(), anyBoolean())).thenReturn(Optional.empty()); DynamoUpload2 dynamoUpload2 = mock(DynamoUpload2.class); when(dynamoUpload2.getMetadata()).thenThrow(new EntityNotFoundException(BridgeEntity.class)); when(dynamoUpload2.getHealthCode()).thenThrow(new EntityNotFoundException(BridgeEntity.class)); when(dynamoUpload2.getAppId()).thenReturn("42"); when(uploadDao.getUploadNoThrow((String) any())).thenReturn(dynamoUpload2); new RuntimeException(); assertThrows(EntityNotFoundException.class, () -> uploadService.getUploadViewForExporter3("42", "42", "42", true, true)); verify(healthDataEx3Service).getRecord((String) any(), anyBoolean()); verify(uploadDao).getUploadNoThrow((String) any()); verify(dynamoUpload2).getAppId(); verify(dynamoUpload2).getHealthCode(); } /** * Method under test: {@link UploadService#getUploadViewForExporter3(String, String, String, boolean, boolean)} */ @Test void testGetUploadViewForExporter39() { when(accountService.getAccountId((String) any(), (String) any())).thenReturn(Optional.of("42")); when(healthDataEx3Service.getRecord((String) any(), anyBoolean())).thenReturn(Optional.empty()); when(uploadDao.getUploadNoThrow((String) any())).thenReturn(mock(DynamoUpload2.class)); new RuntimeException(); assertThrows(BadRequestException.class, () -> uploadService.getUploadViewForExporter3("42", "", "42", true, true)); } /** * Method under test: {@link UploadService#getUploadViewForExporter3(String, String, String, boolean, boolean)} */ @Test void testGetUploadViewForExporter310() { when(accountService.getAccountId((String) any(), (String) any())).thenReturn(Optional.of("42")); when(healthDataEx3Service.getRecord((String) any(), anyBoolean())).thenReturn(Optional.empty()); DynamoUpload2 dynamoUpload2 = mock(DynamoUpload2.class); when(dynamoUpload2.getMetadata()).thenReturn(new ObjectNode(JsonNodeFactory.withExactBigDecimals(true))); when(dynamoUpload2.getHealthCode()).thenReturn("Health Code"); when(dynamoUpload2.getAppId()).thenReturn("42"); when(uploadDao.getUploadNoThrow((String) any())).thenReturn(dynamoUpload2); new RuntimeException(); UploadViewEx3 actualUploadViewForExporter3 = uploadService.getUploadViewForExporter3("42", "42", "42", true, false); assertEquals("42", actualUploadViewForExporter3.getUserId()); assertNull(actualUploadViewForExporter3.getRecord()); assertEquals("42", actualUploadViewForExporter3.getId()); assertEquals("Health Code", actualUploadViewForExporter3.getHealthCode()); verify(accountService).getAccountId((String) any(), (String) any()); verify(healthDataEx3Service).getRecord((String) any(), anyBoolean()); verify(uploadDao).getUploadNoThrow((String) any()); verify(dynamoUpload2).getMetadata(); verify(dynamoUpload2).getAppId(); verify(dynamoUpload2).getHealthCode(); } /** * Method under test: {@link UploadService#getUploadViewForExporter3(String, String, String, boolean, boolean)} */ @Test @Disabled("TODO: Complete this test") void testGetUploadViewForExporter311() { // TODO: Complete this test. // Reason: R013 No inputs found that don't throw a trivial exception. // Diffblue Cover tried to run the arrange/act section, but the method under // test threw // java.lang.NullPointerException // at org.sagebionetworks.bridge.services.UploadService.getUploadViewForExporter3(UploadService.java:574) // See https://diff.blue/R013 to resolve this issue. when(accountService.getAccountId((String) any(), (String) any())).thenReturn(null); when(healthDataEx3Service.getRecord((String) any(), anyBoolean())).thenReturn(Optional.empty()); when(uploadDao.getUploadNoThrow((String) any())).thenReturn(mock(DynamoUpload2.class)); new RuntimeException(); uploadService.getUploadViewForExporter3("42", "42", "42", true, false); } }
DiffBlue at least wrote tests that not only compile, but also succeed! The test coverage is poor though, with only 55% line coverage and 42% branch coverage, and most of the lines it failed to cover were important cases that needed to be tested.
Also, the tests are almost unreadable. I wouldn’t accept this level of quality from a paid intern. I’m not even sure I would accept this level of quality for free.
Conclusion: DiffBlue is worth investigating for simple code chunks, but is a poor fit for anything more complex.
Code Reviews
AI seems to have trouble generating code, but it might be able to help with code reviews.
Personally, I think AI-assisted code review shouldn’t replace human-led code review. But I think the two pieces of value are
catching things that human reviewers might miss and
catching things early so that developers can start fixing issues in their code instead of waiting for teammates’ availability.
I’m not sure how we could measure it, but I conjecture that if we point an AI at our code and it catches issues at all (other than inconsequential issues or stylistic nitpicks), then it’s providing value. Whether that value is worth what we’re being charged for it remains to be seen, but AWS CodeGuru charges $30/mo for each 100k lines of code, which is less than 0.5% of an entry-level Associate Engineer’s salary at Sage.
Leads include
Codeball
Codeball runs in your GitHub repository as a GitHub action. However, I ran into permissions issues setting up GitHub and was not able to resolve them on my own. It looks like Codeball support is done through Discord, and the Codeball devs aren’t very active there. As a result, I am currently blocked and unable to evaluate Codeball any further.
In addition to Discord, I also tried creating a GitHub issue for support https://github.com/sturdy-dev/codeball-action/issues/186
Codiga
Codiga runs as a GitHub app. Installing the Codiga app requires org-level permissions, even if you’re only installing for repositories that you have admin permissions on. This is currently waiting on IT to respond to the installation request.
See - IT-2753Getting issue details... STATUS for tracking.
AWS CodeGuru
See https://aws.amazon.com/codeguru/
There’s an issue where CodeGuru tries to associate with my fork at https://github.com/DwayneJengSage/BridgeServer2 instead of the root fork at https://github.com/Sage-Bionetworks/BridgeServer2, which means I’m unable to point CodeGuru at our pull requests.
I filed an AWS support request https://support.console.aws.amazon.com/support/home?region=us-east-1#/case/?displayId=12290789771&language=en